2015-11-05 13:52:33 +01:00
|
|
|
#!/usr/bin/env python
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# A library that provides a Python interface to the Telegram Bot API
|
2022-01-03 08:15:18 +01:00
|
|
|
# Copyright (C) 2015-2022
|
2016-01-05 14:12:03 +01:00
|
|
|
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# 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/].
|
2017-09-01 08:43:08 +02:00
|
|
|
"""This module contains the class Updater, which tries to make creating Telegram bots intuitive."""
|
2016-01-13 17:09:35 +01:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
import logging
|
2015-11-16 13:05:57 +01:00
|
|
|
import ssl
|
2020-07-19 17:47:26 +02:00
|
|
|
import warnings
|
2016-04-25 09:01:21 +02:00
|
|
|
from queue import Queue
|
2020-10-31 16:33:34 +01:00
|
|
|
from signal import SIGABRT, SIGINT, SIGTERM, signal
|
|
|
|
from threading import Event, Lock, Thread, current_thread
|
|
|
|
from time import sleep
|
2021-06-06 10:37:53 +02:00
|
|
|
from typing import (
|
|
|
|
TYPE_CHECKING,
|
|
|
|
Any,
|
|
|
|
Callable,
|
|
|
|
Dict,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Tuple,
|
|
|
|
Union,
|
|
|
|
no_type_check,
|
|
|
|
Generic,
|
|
|
|
overload,
|
|
|
|
)
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2016-08-20 22:01:07 +02:00
|
|
|
from telegram import Bot, TelegramError
|
2020-10-31 16:33:34 +01:00
|
|
|
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized
|
2021-06-06 11:48:48 +02:00
|
|
|
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot
|
2021-05-29 16:18:16 +02:00
|
|
|
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated
|
2021-06-06 11:48:48 +02:00
|
|
|
from telegram.utils.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue
|
2016-09-06 15:38:07 +02:00
|
|
|
from telegram.utils.request import Request
|
2021-06-06 10:37:53 +02:00
|
|
|
from telegram.ext.utils.types import CCT, UD, CD, BD
|
2021-01-30 14:15:39 +01:00
|
|
|
from telegram.ext.utils.webhookhandler import WebhookAppClass, WebhookServer
|
2020-10-06 19:28:40 +02:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2021-06-06 10:37:53 +02:00
|
|
|
from telegram.ext import BasePersistence, Defaults, CallbackContext
|
2020-10-06 19:28:40 +02:00
|
|
|
|
2015-11-06 00:24:01 +01:00
|
|
|
|
2021-06-06 10:37:53 +02:00
|
|
|
class Updater(Generic[CCT, UD, CD, BD]):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2017-07-23 22:33:08 +02:00
|
|
|
This class, which employs the :class:`telegram.ext.Dispatcher`, provides a frontend to
|
|
|
|
:class:`telegram.Bot` to the programmer, so they can focus on coding the bot. Its purpose is to
|
|
|
|
receive the updates from Telegram and to deliver them to said dispatcher. It also runs in a
|
|
|
|
separate thread, so the user can interact with the bot, for example on the command line. The
|
|
|
|
dispatcher supports handlers for different kinds of data: Updates from Telegram, basic text
|
|
|
|
commands and even arbitrary types. The updater can be started as a polling service or, for
|
|
|
|
production, use a webhook to receive updates. This is achieved using the WebhookServer and
|
2015-11-22 19:15:17 +01:00
|
|
|
WebhookHandler classes.
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2020-12-30 15:59:50 +01:00
|
|
|
Note:
|
|
|
|
* You must supply either a :attr:`bot` or a :attr:`token` argument.
|
2021-06-06 11:48:48 +02:00
|
|
|
* If you supply a :attr:`bot`, you will need to pass :attr:`arbitrary_callback_data`,
|
|
|
|
and :attr:`defaults` to the bot instead of the :class:`telegram.ext.Updater`. In this
|
|
|
|
case, you'll have to use the class :class:`telegram.ext.ExtBot`.
|
|
|
|
|
|
|
|
.. versionchanged:: 13.6
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
token (:obj:`str`, optional): The bot's token given by the @BotFather.
|
|
|
|
base_url (:obj:`str`, optional): Base_url for the bot.
|
2020-02-02 22:47:02 +01:00
|
|
|
base_file_url (:obj:`str`, optional): Base_file_url for the bot.
|
2017-07-23 22:33:08 +02:00
|
|
|
workers (:obj:`int`, optional): Amount of threads in the thread pool for functions
|
2020-01-26 20:59:47 +01:00
|
|
|
decorated with ``@run_async`` (ignored if `dispatcher` argument is used).
|
|
|
|
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if
|
|
|
|
`dispatcher` argument is used). If a pre-initialized bot is used, it is the user's
|
|
|
|
responsibility to create it using a `Request` instance with a large enough connection
|
|
|
|
pool.
|
|
|
|
dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher
|
|
|
|
instance. If a pre-initialized dispatcher is used, it is the user's responsibility to
|
|
|
|
create it with proper arguments.
|
Bot API 4.0 (#1168)
Telegram Passport (#1174):
- Add full support for telegram passport.
- New types: PassportData, PassportFile, EncryptedPassportElement, EncryptedCredentials, PassportElementError, PassportElementErrorDataField, PassportElementErrorFrontSide, PassportElementErrorReverseSide, PassportElementErrorSelfie, PassportElementErrorFile and PassportElementErrorFiles.
- New bot method: set_passport_data_errors
- New filter: Filters.passport_data
- Field passport_data field on Message
- PassportData is automagically decrypted when you specify your private key when creating Updater or Bot.
- PassportFiles is also automagically decrypted as you download/retrieve them.
- See new passportbot.py example for details on how to use, or go to our telegram passport wiki page for more info
- NOTE: Passport decryption requires new dependency `cryptography`.
Inputfile rework (#1184):
- Change how Inputfile is handled internally
- This allows support for specifying the thumbnails of photos and videos using the thumb= argument in the different send_ methods.
- Also allows Bot.send_media_group to actually finally send more than one media.
- Add thumb to Audio, Video and Videonote
- Add Bot.edit_message_media together with InputMediaAnimation, InputMediaAudio, and inputMediaDocument.
Other Bot API 4.0 changes:
- Add forusquare_type to Venue, InlineQueryResultVenue, InputVenueMessageContent, and Bot.send_venue. (#1170)
- Add vCard support by adding vcard field to Contact, InlineQueryResultContact, InputContactMessageContent, and Bot.send_contact. (#1166)
- Support new message entities: CASHTAG and PHONE_NUMBER. (#1179)
- Cashtag seems to be things like $USD and $GBP, but it seems telegram doesn't currently send them to bots.
- Phone number also seems to have limited support for now
- Add Bot.send_animation, add width, height, and duration to Animation, and add Filters.animation. (#1172)
Co-authored-by: Jasmin Bom <jsmnbom@gmail.com>
Co-authored-by: code1mountain <32801117+code1mountain@users.noreply.github.com>
Co-authored-by: Eldinnie <pieter.schutz+github@gmail.com>
Co-authored-by: mathefreak1 <mathefreak@hi2.in>
2018-08-29 14:18:58 +02:00
|
|
|
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
|
|
|
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
2017-07-23 22:33:08 +02:00
|
|
|
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
|
|
|
|
arguments. This will be called when a signal is received, defaults are (SIGINT,
|
Documentation Improvements (#2008)
* Minor doc updates, following official API docs
* Fix spelling in Defaults docstrings
* Clarify Changelog of v12.7 about aware dates
* Fix typo in CHANGES.rst (#2024)
* Fix PicklePersistence.flush() with only bot_data (#2017)
* Update pylint in pre-commit to fix CI (#2018)
* Add Filters.via_bot (#2009)
* feat: via_bot filter
also fixing a small mistake in the empty parameter of the user filter and improve docs slightly
* fix: forgot to set via_bot to None
* fix: redoing subclassing to copy paste solution
* Cosmetic changes
Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
* Update CHANGES.rst
Fixed Typo
Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
* Update downloads badge, add info on IRC Channel to Getting Help section
* Remove RegexHandler from ConversationHandlers Docs (#1973)
Replaced RegexHandler with MessageHandler, since the former is deprecated
* Fix Filters.via_bot docstrings
* Add notes on Markdown v1 being legacy mode
* Fixed typo in the Regex doc.. (#2036)
* Typo: Spelling
* Minor cleanup from #2043
* Document CommandHandler ignoring channel posts
* Doc fixes for a few telegram.ext classes
* Doc fixes for most `telegram` classes.
* pep-8
forgot the hard wrap is at 99 chars, not 100!
fixed a few spelling mistakes too.
* Address review and made rendering of booleans consistent
True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes.
* Few doc fixes for inline* classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram/files classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram.Game
Mostly just added hyperlinks. And fixed message length doc.
As usual, docs were cross-checked with official tg api docs.
* Very minor doc fix for passportfile.py and passportelementerrors.py
Didn't bother changing too much since this seems to be a custom implementation.
* Doc fixes for telegram.payments
As usual, cross-checked with official bot api docs.
* Address review 2
Few tiny other fixes too.
* Changed from ``True/False/None`` to :obj:`True/False/None` project-wide.
Few tiny other doc fixes too.
Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com>
Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com>
Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
|
|
|
SIGTERM, SIGABRT) settable with :attr:`idle`.
|
2017-10-11 23:39:09 +02:00
|
|
|
request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a
|
2020-01-26 20:59:47 +01:00
|
|
|
`telegram.utils.request.Request` object (ignored if `bot` or `dispatcher` argument is
|
|
|
|
used). The request_kwargs are very useful for the advanced users who would like to
|
|
|
|
control the default timeouts and/or control the proxy used for http communication.
|
2020-08-16 16:36:05 +02:00
|
|
|
use_context (:obj:`bool`, optional): If set to :obj:`True` uses the context based callback
|
|
|
|
API (ignored if `dispatcher` argument is used). Defaults to :obj:`True`.
|
|
|
|
**New users**: set this to :obj:`True`.
|
2018-09-20 22:50:40 +02:00
|
|
|
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
2020-01-26 20:59:47 +01:00
|
|
|
store data that should be persistent over restarts (ignored if `dispatcher` argument is
|
|
|
|
used).
|
2020-02-06 11:22:56 +01:00
|
|
|
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
|
|
|
be used if not set explicitly in the bot methods.
|
2021-06-06 11:48:48 +02:00
|
|
|
arbitrary_callback_data (:obj:`bool` | :obj:`int` | :obj:`None`, optional): Whether to
|
|
|
|
allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`.
|
|
|
|
Pass an integer to specify the maximum number of cached objects. For more details,
|
|
|
|
please see our wiki. Defaults to :obj:`False`.
|
|
|
|
|
|
|
|
.. versionadded:: 13.6
|
2021-06-06 10:37:53 +02:00
|
|
|
context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance
|
|
|
|
of :class:`telegram.ext.ContextTypes` to customize the types used in the
|
|
|
|
``context`` interface. If not passed, the defaults documented in
|
|
|
|
:class:`telegram.ext.ContextTypes` will be used.
|
|
|
|
|
|
|
|
.. versionadded:: 13.6
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2015-12-26 18:32:36 +01:00
|
|
|
Raises:
|
2017-07-23 22:33:08 +02:00
|
|
|
ValueError: If both :attr:`token` and :attr:`bot` are passed or none of them.
|
2017-09-01 08:43:08 +02:00
|
|
|
|
2020-12-30 15:59:50 +01:00
|
|
|
|
|
|
|
Attributes:
|
|
|
|
bot (:class:`telegram.Bot`): The bot used with this Updater.
|
|
|
|
user_sig_handler (:obj:`function`): Optional. Function to be called when a signal is
|
|
|
|
received.
|
|
|
|
update_queue (:obj:`Queue`): Queue for the updates.
|
|
|
|
job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater.
|
|
|
|
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and
|
|
|
|
dispatches them to the handlers.
|
|
|
|
running (:obj:`bool`): Indicates if the updater is running.
|
|
|
|
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
|
|
|
store data that should be persistent over restarts.
|
|
|
|
use_context (:obj:`bool`): Optional. :obj:`True` if using context based callbacks.
|
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2021-05-29 16:18:16 +02:00
|
|
|
__slots__ = (
|
|
|
|
'persistence',
|
|
|
|
'dispatcher',
|
|
|
|
'user_sig_handler',
|
|
|
|
'bot',
|
|
|
|
'logger',
|
|
|
|
'update_queue',
|
|
|
|
'job_queue',
|
|
|
|
'__exception_event',
|
|
|
|
'last_update_id',
|
|
|
|
'running',
|
|
|
|
'_request',
|
|
|
|
'is_idle',
|
|
|
|
'httpd',
|
|
|
|
'__lock',
|
|
|
|
'__threads',
|
|
|
|
'__dict__',
|
|
|
|
)
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2021-06-06 10:37:53 +02:00
|
|
|
@overload
|
2020-10-09 17:22:07 +02:00
|
|
|
def __init__(
|
2021-06-06 10:37:53 +02:00
|
|
|
self: 'Updater[CallbackContext, dict, dict, dict]',
|
|
|
|
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, # pylint: disable=E0601
|
|
|
|
defaults: 'Defaults' = None,
|
|
|
|
use_context: bool = True,
|
|
|
|
base_file_url: str = None,
|
2021-06-06 11:48:48 +02:00
|
|
|
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
2021-06-06 10:37:53 +02:00
|
|
|
):
|
|
|
|
...
|
|
|
|
|
|
|
|
@overload
|
|
|
|
def __init__(
|
|
|
|
self: 'Updater[CCT, UD, CD, BD]',
|
|
|
|
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,
|
|
|
|
base_file_url: str = None,
|
2021-06-06 11:48:48 +02:00
|
|
|
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
2021-06-06 10:37:53 +02:00
|
|
|
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
|
|
|
):
|
|
|
|
...
|
|
|
|
|
|
|
|
@overload
|
|
|
|
def __init__(
|
|
|
|
self: 'Updater[CCT, UD, CD, BD]',
|
|
|
|
user_sig_handler: Callable = None,
|
|
|
|
dispatcher: Dispatcher[CCT, UD, CD, BD] = None,
|
|
|
|
):
|
|
|
|
...
|
|
|
|
|
|
|
|
def __init__( # type: ignore[no-untyped-def,misc]
|
2020-10-09 17:22:07 +02:00
|
|
|
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,
|
2021-06-06 10:37:53 +02:00
|
|
|
dispatcher=None,
|
2020-10-09 17:22:07 +02:00
|
|
|
base_file_url: str = None,
|
2021-06-06 11:48:48 +02:00
|
|
|
arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE,
|
2021-06-06 10:37:53 +02:00
|
|
|
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
2020-10-09 17:22:07 +02:00
|
|
|
):
|
2020-01-26 20:59:47 +01:00
|
|
|
|
2020-07-19 17:47:26 +02:00
|
|
|
if defaults and bot:
|
2020-10-09 17:22:07 +02:00
|
|
|
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,
|
|
|
|
)
|
2021-06-06 11:48:48 +02:00
|
|
|
if arbitrary_callback_data is not DEFAULT_FALSE and bot:
|
|
|
|
warnings.warn(
|
|
|
|
'Passing arbitrary_callback_data to an Updater has no '
|
|
|
|
'effect when a Bot is passed as well. Pass them to the Bot instead.',
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
2020-07-19 17:47:26 +02:00
|
|
|
|
2020-01-26 20:59:47 +01:00
|
|
|
if dispatcher is None:
|
|
|
|
if (token is None) and (bot is None):
|
|
|
|
raise ValueError('`token` or `bot` must be passed')
|
|
|
|
if (token is not None) and (bot is not None):
|
|
|
|
raise ValueError('`token` and `bot` are mutually exclusive')
|
|
|
|
if (private_key is not None) and (bot is not None):
|
|
|
|
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
|
|
|
else:
|
|
|
|
if bot is not None:
|
|
|
|
raise ValueError('`dispatcher` and `bot` are mutually exclusive')
|
|
|
|
if persistence is not None:
|
|
|
|
raise ValueError('`dispatcher` and `persistence` are mutually exclusive')
|
|
|
|
if use_context != dispatcher.use_context:
|
|
|
|
raise ValueError('`dispatcher` and `use_context` are mutually exclusive')
|
2021-06-06 10:37:53 +02:00
|
|
|
if context_types is not None:
|
|
|
|
raise ValueError('`dispatcher` and `context_types` are mutually exclusive')
|
|
|
|
if workers is not None:
|
|
|
|
raise ValueError('`dispatcher` and `workers` are mutually exclusive')
|
2015-11-22 13:30:23 +01:00
|
|
|
|
2017-08-12 15:45:38 +02:00
|
|
|
self.logger = logging.getLogger(__name__)
|
2021-05-29 16:18:16 +02:00
|
|
|
self._request = None
|
2017-08-12 15:45:38 +02:00
|
|
|
|
2020-01-26 20:59:47 +01:00
|
|
|
if dispatcher is None:
|
|
|
|
con_pool_size = workers + 4
|
|
|
|
|
|
|
|
if bot is not None:
|
|
|
|
self.bot = bot
|
|
|
|
if bot.request.con_pool_size < con_pool_size:
|
|
|
|
self.logger.warning(
|
|
|
|
'Connection pool of Request object is smaller than optimal value (%s)',
|
2020-10-09 17:22:07 +02:00
|
|
|
con_pool_size,
|
|
|
|
)
|
2020-01-26 20:59:47 +01:00
|
|
|
else:
|
|
|
|
# we need a connection pool the size of:
|
|
|
|
# * for each of the workers
|
|
|
|
# * 1 for Dispatcher
|
|
|
|
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
|
|
|
# * 1 for JobQueue
|
|
|
|
# * 1 for main thread
|
|
|
|
if request_kwargs is None:
|
|
|
|
request_kwargs = {}
|
|
|
|
if 'con_pool_size' not in request_kwargs:
|
|
|
|
request_kwargs['con_pool_size'] = con_pool_size
|
|
|
|
self._request = Request(**request_kwargs)
|
2021-06-06 11:48:48 +02:00
|
|
|
self.bot = ExtBot(
|
2020-10-09 17:22:07 +02:00
|
|
|
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,
|
2021-06-06 11:48:48 +02:00
|
|
|
arbitrary_callback_data=(
|
|
|
|
False # type: ignore[arg-type]
|
|
|
|
if arbitrary_callback_data is DEFAULT_FALSE
|
|
|
|
else arbitrary_callback_data
|
|
|
|
),
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2020-10-06 19:28:40 +02:00
|
|
|
self.update_queue: Queue = Queue()
|
2020-01-26 20:59:47 +01:00
|
|
|
self.job_queue = JobQueue()
|
|
|
|
self.__exception_event = Event()
|
|
|
|
self.persistence = persistence
|
2020-10-09 17:22:07 +02:00
|
|
|
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,
|
2021-06-06 10:37:53 +02:00
|
|
|
context_types=context_types,
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2020-01-26 20:59:47 +01:00
|
|
|
self.job_queue.set_dispatcher(self.dispatcher)
|
|
|
|
else:
|
|
|
|
con_pool_size = dispatcher.workers + 4
|
2017-08-12 15:45:38 +02:00
|
|
|
|
2020-01-26 20:59:47 +01:00
|
|
|
self.bot = dispatcher.bot
|
|
|
|
if self.bot.request.con_pool_size < con_pool_size:
|
2017-08-12 15:45:38 +02:00
|
|
|
self.logger.warning(
|
|
|
|
'Connection pool of Request object is smaller than optimal value (%s)',
|
2020-10-09 17:22:07 +02:00
|
|
|
con_pool_size,
|
|
|
|
)
|
2020-01-26 20:59:47 +01:00
|
|
|
self.update_queue = dispatcher.update_queue
|
|
|
|
self.__exception_event = dispatcher.exception_event
|
|
|
|
self.persistence = dispatcher.persistence
|
|
|
|
self.job_queue = dispatcher.job_queue
|
|
|
|
self.dispatcher = dispatcher
|
|
|
|
|
2017-03-26 14:36:13 +02:00
|
|
|
self.user_sig_handler = user_sig_handler
|
2015-11-22 13:30:23 +01:00
|
|
|
self.last_update_id = 0
|
2015-11-15 17:36:38 +01:00
|
|
|
self.running = False
|
2015-11-23 03:45:47 +01:00
|
|
|
self.is_idle = False
|
2015-11-16 13:05:57 +01:00
|
|
|
self.httpd = None
|
2015-12-31 14:52:28 +01:00
|
|
|
self.__lock = Lock()
|
2020-10-06 19:28:40 +02:00
|
|
|
self.__threads: List[Thread] = []
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2021-05-29 16:18:16 +02:00
|
|
|
def __setattr__(self, key: str, value: object) -> None:
|
|
|
|
if key.startswith('__'):
|
|
|
|
key = f"_{self.__class__.__name__}{key}"
|
|
|
|
if issubclass(self.__class__, Updater) and self.__class__ is not Updater:
|
|
|
|
object.__setattr__(self, key, value)
|
|
|
|
return
|
|
|
|
set_new_attribute_deprecated(self, key, value)
|
|
|
|
|
2021-01-30 11:38:54 +01:00
|
|
|
def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None:
|
2020-10-09 17:22:07 +02:00
|
|
|
thr = Thread(
|
|
|
|
target=self._thread_wrapper,
|
2020-11-23 22:09:29 +01:00
|
|
|
name=f"Bot:{self.bot.id}:{name}",
|
2020-10-09 17:22:07 +02:00
|
|
|
args=(target,) + args,
|
|
|
|
kwargs=kwargs,
|
|
|
|
)
|
2016-05-10 23:58:55 +02:00
|
|
|
thr.start()
|
|
|
|
self.__threads.append(thr)
|
|
|
|
|
2021-01-30 11:38:54 +01:00
|
|
|
def _thread_wrapper(self, target: Callable, *args: object, **kwargs: object) -> None:
|
2016-05-10 23:58:55 +02:00
|
|
|
thr_name = current_thread().name
|
2020-10-31 16:33:34 +01:00
|
|
|
self.logger.debug('%s - started', thr_name)
|
2016-05-10 23:58:55 +02:00
|
|
|
try:
|
|
|
|
target(*args, **kwargs)
|
|
|
|
except Exception:
|
|
|
|
self.__exception_event.set()
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.exception('unhandled exception in %s', thr_name)
|
2016-05-10 23:58:55 +02:00
|
|
|
raise
|
2020-10-31 16:33:34 +01:00
|
|
|
self.logger.debug('%s - ended', thr_name)
|
2016-05-10 23:58:55 +02:00
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
def start_polling(
|
|
|
|
self,
|
|
|
|
poll_interval: float = 0.0,
|
|
|
|
timeout: float = 10,
|
2021-03-13 15:35:26 +01:00
|
|
|
clean: bool = None,
|
2020-10-09 17:22:07 +02:00
|
|
|
bootstrap_retries: int = -1,
|
|
|
|
read_latency: float = 2.0,
|
|
|
|
allowed_updates: List[str] = None,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates: bool = None,
|
2020-10-09 17:22:07 +02:00
|
|
|
) -> Optional[Queue]:
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Starts polling updates from Telegram.
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
poll_interval (:obj:`float`, optional): Time to wait between polling updates from
|
2021-04-30 10:47:41 +02:00
|
|
|
Telegram in seconds. Default is ``0.0``.
|
2021-03-13 15:35:26 +01:00
|
|
|
timeout (:obj:`float`, optional): Passed to :meth:`telegram.Bot.get_updates`.
|
|
|
|
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
|
|
|
|
Telegram servers before actually starting to poll. Default is :obj:`False`.
|
|
|
|
|
|
|
|
.. versionadded :: 13.4
|
|
|
|
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
|
|
|
|
|
|
|
|
.. deprecated:: 13.4
|
|
|
|
Use ``drop_pending_updates`` instead.
|
2017-07-23 22:33:08 +02:00
|
|
|
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
|
2021-03-13 15:35:26 +01:00
|
|
|
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
* < 0 - retry indefinitely (default)
|
|
|
|
* 0 - no retries
|
2017-07-23 22:33:08 +02:00
|
|
|
* > 0 - retry up to X times
|
|
|
|
|
|
|
|
allowed_updates (List[:obj:`str`], optional): Passed to
|
2021-03-13 15:35:26 +01:00
|
|
|
:meth:`telegram.Bot.get_updates`.
|
2017-07-23 22:33:08 +02:00
|
|
|
read_latency (:obj:`float` | :obj:`int`, optional): Grace time in seconds for receiving
|
2021-03-13 15:35:26 +01:00
|
|
|
the reply from server. Will be added to the ``timeout`` value and used as the read
|
2021-04-30 10:47:41 +02:00
|
|
|
timeout from server (Default: ``2``).
|
2017-01-06 22:48:34 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Returns:
|
2017-07-23 22:33:08 +02:00
|
|
|
:obj:`Queue`: The update queue that can be filled from the main thread.
|
2017-09-01 08:43:08 +02:00
|
|
|
|
2016-03-01 20:40:04 +01:00
|
|
|
"""
|
2021-03-13 15:35:26 +01:00
|
|
|
if (clean is not None) and (drop_pending_updates is not None):
|
|
|
|
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
|
|
|
|
|
|
|
|
if clean is not None:
|
|
|
|
warnings.warn(
|
|
|
|
'The argument `clean` of `start_polling` is deprecated. Please use '
|
|
|
|
'`drop_pending_updates` instead.',
|
|
|
|
category=TelegramDeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
|
|
|
|
|
|
|
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
|
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
|
|
|
if not self.running:
|
|
|
|
self.running = True
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-02-06 11:38:56 +01:00
|
|
|
# Create & start threads
|
2016-09-27 09:21:35 +02:00
|
|
|
self.job_queue.start()
|
2017-10-21 13:40:24 +02:00
|
|
|
dispatcher_ready = Event()
|
2020-08-25 22:21:24 +02:00
|
|
|
polling_ready = Event()
|
2017-10-21 13:40:24 +02:00
|
|
|
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
2020-10-09 17:22:07 +02:00
|
|
|
self._init_thread(
|
|
|
|
self._start_polling,
|
|
|
|
"updater",
|
|
|
|
poll_interval,
|
|
|
|
timeout,
|
|
|
|
read_latency,
|
|
|
|
bootstrap_retries,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates,
|
2020-10-09 17:22:07 +02:00
|
|
|
allowed_updates,
|
|
|
|
ready=polling_ready,
|
|
|
|
)
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2020-08-25 22:21:24 +02:00
|
|
|
self.logger.debug('Waiting for Dispatcher and polling to start')
|
2017-10-21 13:40:24 +02:00
|
|
|
dispatcher_ready.wait()
|
2020-08-25 22:21:24 +02:00
|
|
|
polling_ready.wait()
|
2017-10-21 13:40:24 +02:00
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
# Return the update queue so the main thread can insert updates
|
|
|
|
return self.update_queue
|
2020-10-06 19:28:40 +02:00
|
|
|
return None
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
def start_webhook(
|
|
|
|
self,
|
|
|
|
listen: str = '127.0.0.1',
|
|
|
|
port: int = 80,
|
|
|
|
url_path: str = '',
|
|
|
|
cert: str = None,
|
|
|
|
key: str = None,
|
2021-03-13 15:35:26 +01:00
|
|
|
clean: bool = None,
|
2020-10-09 17:22:07 +02:00
|
|
|
bootstrap_retries: int = 0,
|
|
|
|
webhook_url: str = None,
|
|
|
|
allowed_updates: List[str] = None,
|
2021-05-05 20:59:06 +02:00
|
|
|
force_event_loop: bool = None,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates: bool = None,
|
|
|
|
ip_address: str = None,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections: int = 40,
|
2020-10-09 17:22:07 +02:00
|
|
|
) -> Optional[Queue]:
|
2015-11-16 13:05:57 +01:00
|
|
|
"""
|
2021-04-30 10:47:41 +02:00
|
|
|
Starts a small http server to listen for updates via webhook. If :attr:`cert`
|
|
|
|
and :attr:`key` are not provided, the webhook will be started directly on
|
2015-12-01 15:16:04 +01:00
|
|
|
http://listen:port/url_path, so SSL can be handled by another
|
|
|
|
application. Else, the webhook will be started on
|
2021-03-13 15:35:26 +01:00
|
|
|
https://listen:port/url_path. Also calls :meth:`telegram.Bot.set_webhook` as required.
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2021-04-30 10:47:41 +02:00
|
|
|
.. versionchanged:: 13.4
|
|
|
|
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
|
|
|
|
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.
|
|
|
|
|
2015-11-16 13:05:57 +01:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
listen (:obj:`str`, optional): IP-Address to listen on. Default ``127.0.0.1``.
|
|
|
|
port (:obj:`int`, optional): Port the bot should be listening on. Default ``80``.
|
|
|
|
url_path (:obj:`str`, optional): Path inside url.
|
|
|
|
cert (:obj:`str`, optional): Path to the SSL certificate file.
|
|
|
|
key (:obj:`str`, optional): Path to the SSL key file.
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
|
|
|
|
Telegram servers before actually starting to poll. Default is :obj:`False`.
|
|
|
|
|
|
|
|
.. versionadded :: 13.4
|
|
|
|
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
|
|
|
|
|
|
|
|
.. deprecated:: 13.4
|
|
|
|
Use ``drop_pending_updates`` instead.
|
2017-08-02 04:56:07 +02:00
|
|
|
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
|
2021-03-13 15:35:26 +01:00
|
|
|
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
* < 0 - retry indefinitely (default)
|
|
|
|
* 0 - no retries
|
2017-07-23 22:33:08 +02:00
|
|
|
* > 0 - retry up to X times
|
|
|
|
|
|
|
|
webhook_url (:obj:`str`, optional): Explicitly specify the webhook url. Useful behind
|
2021-03-13 15:35:26 +01:00
|
|
|
NAT, reverse proxy, etc. Default is derived from ``listen``, ``port`` &
|
|
|
|
``url_path``.
|
|
|
|
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
|
|
|
|
|
|
|
|
.. versionadded :: 13.4
|
2017-07-23 22:33:08 +02:00
|
|
|
allowed_updates (List[:obj:`str`], optional): Passed to
|
2021-03-13 15:35:26 +01:00
|
|
|
:meth:`telegram.Bot.set_webhook`.
|
2021-05-05 20:59:06 +02:00
|
|
|
force_event_loop (:obj:`bool`, optional): Legacy parameter formerly used for a
|
|
|
|
workaround on Windows + Python 3.8+. No longer has any effect.
|
|
|
|
|
|
|
|
.. deprecated:: 13.6
|
|
|
|
Since version 13.6, ``tornade>=6.1`` is required, which resolves the former
|
|
|
|
issue.
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections (:obj:`int`, optional): Passed to
|
|
|
|
:meth:`telegram.Bot.set_webhook`.
|
|
|
|
|
|
|
|
.. versionadded:: 13.6
|
|
|
|
|
2015-11-16 13:05:57 +01:00
|
|
|
Returns:
|
2017-07-23 22:33:08 +02:00
|
|
|
:obj:`Queue`: The update queue that can be filled from the main thread.
|
2017-09-01 08:43:08 +02:00
|
|
|
|
2016-03-14 00:33:09 +01:00
|
|
|
"""
|
2021-03-13 15:35:26 +01:00
|
|
|
if (clean is not None) and (drop_pending_updates is not None):
|
|
|
|
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
|
|
|
|
|
|
|
|
if clean is not None:
|
|
|
|
warnings.warn(
|
|
|
|
'The argument `clean` of `start_webhook` is deprecated. Please use '
|
|
|
|
'`drop_pending_updates` instead.',
|
|
|
|
category=TelegramDeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
|
|
|
|
2021-05-05 20:59:06 +02:00
|
|
|
if force_event_loop is not None:
|
|
|
|
warnings.warn(
|
|
|
|
'The argument `force_event_loop` of `start_webhook` is deprecated and no longer '
|
|
|
|
'has any effect.',
|
|
|
|
category=TelegramDeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
|
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
|
|
|
if not self.running:
|
|
|
|
self.running = True
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2016-02-09 22:05:50 +01:00
|
|
|
# Create & start threads
|
2020-08-25 22:21:24 +02:00
|
|
|
webhook_ready = Event()
|
|
|
|
dispatcher_ready = Event()
|
2016-09-27 09:21:35 +02:00
|
|
|
self.job_queue.start()
|
2020-08-25 22:21:24 +02:00
|
|
|
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
2020-10-09 17:22:07 +02:00
|
|
|
self._init_thread(
|
|
|
|
self._start_webhook,
|
|
|
|
"updater",
|
|
|
|
listen,
|
|
|
|
port,
|
|
|
|
url_path,
|
|
|
|
cert,
|
|
|
|
key,
|
|
|
|
bootstrap_retries,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates,
|
2020-10-09 17:22:07 +02:00
|
|
|
webhook_url,
|
|
|
|
allowed_updates,
|
|
|
|
ready=webhook_ready,
|
2021-03-13 15:35:26 +01:00
|
|
|
ip_address=ip_address,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections=max_connections,
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2020-08-25 22:21:24 +02:00
|
|
|
|
|
|
|
self.logger.debug('Waiting for Dispatcher and Webhook to start')
|
|
|
|
webhook_ready.wait()
|
|
|
|
dispatcher_ready.wait()
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
# Return the update queue so the main thread can insert updates
|
|
|
|
return self.update_queue
|
2020-10-06 19:28:40 +02:00
|
|
|
return None
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
2020-10-09 17:22:07 +02:00
|
|
|
def _start_polling(
|
|
|
|
self,
|
|
|
|
poll_interval,
|
|
|
|
timeout,
|
|
|
|
read_latency,
|
|
|
|
bootstrap_retries,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates,
|
2020-10-09 17:22:07 +02:00
|
|
|
allowed_updates,
|
|
|
|
ready=None,
|
|
|
|
): # pragma: no cover
|
2017-07-23 22:33:08 +02:00
|
|
|
# Thread target of thread 'updater'. Runs in background, pulls
|
|
|
|
# updates from Telegram and inserts them in the update queue of the
|
|
|
|
# Dispatcher.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.debug('Updater thread started (polling)')
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
self._bootstrap(
|
|
|
|
bootstrap_retries,
|
|
|
|
drop_pending_updates=drop_pending_updates,
|
|
|
|
webhook_url='',
|
|
|
|
allowed_updates=None,
|
|
|
|
)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.debug('Bootstrap done')
|
|
|
|
|
|
|
|
def polling_action_cb():
|
2020-10-09 17:22:07 +02:00
|
|
|
updates = self.bot.get_updates(
|
|
|
|
self.last_update_id,
|
|
|
|
timeout=timeout,
|
|
|
|
read_latency=read_latency,
|
|
|
|
allowed_updates=allowed_updates,
|
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
|
|
|
|
if updates:
|
|
|
|
if not self.running:
|
|
|
|
self.logger.debug('Updates ignored and will be pulled again on restart')
|
|
|
|
else:
|
|
|
|
for update in updates:
|
|
|
|
self.update_queue.put(update)
|
|
|
|
self.last_update_id = updates[-1].update_id + 1
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def polling_onerr_cb(exc):
|
|
|
|
# Put the error into the update queue and let the Dispatcher
|
|
|
|
# broadcast it
|
|
|
|
self.update_queue.put(exc)
|
|
|
|
|
2020-08-25 22:21:24 +02:00
|
|
|
if ready is not None:
|
|
|
|
ready.set()
|
|
|
|
|
2020-10-09 17:22:07 +02:00
|
|
|
self._network_loop_retry(
|
|
|
|
polling_action_cb, polling_onerr_cb, 'getting Updates', poll_interval
|
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
2018-03-02 22:11:16 +01:00
|
|
|
def _network_loop_retry(self, action_cb, onerr_cb, description, interval):
|
|
|
|
"""Perform a loop calling `action_cb`, retrying after network errors.
|
|
|
|
|
Documentation Improvements (#2008)
* Minor doc updates, following official API docs
* Fix spelling in Defaults docstrings
* Clarify Changelog of v12.7 about aware dates
* Fix typo in CHANGES.rst (#2024)
* Fix PicklePersistence.flush() with only bot_data (#2017)
* Update pylint in pre-commit to fix CI (#2018)
* Add Filters.via_bot (#2009)
* feat: via_bot filter
also fixing a small mistake in the empty parameter of the user filter and improve docs slightly
* fix: forgot to set via_bot to None
* fix: redoing subclassing to copy paste solution
* Cosmetic changes
Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
* Update CHANGES.rst
Fixed Typo
Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
* Update downloads badge, add info on IRC Channel to Getting Help section
* Remove RegexHandler from ConversationHandlers Docs (#1973)
Replaced RegexHandler with MessageHandler, since the former is deprecated
* Fix Filters.via_bot docstrings
* Add notes on Markdown v1 being legacy mode
* Fixed typo in the Regex doc.. (#2036)
* Typo: Spelling
* Minor cleanup from #2043
* Document CommandHandler ignoring channel posts
* Doc fixes for a few telegram.ext classes
* Doc fixes for most `telegram` classes.
* pep-8
forgot the hard wrap is at 99 chars, not 100!
fixed a few spelling mistakes too.
* Address review and made rendering of booleans consistent
True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes.
* Few doc fixes for inline* classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram/files classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram.Game
Mostly just added hyperlinks. And fixed message length doc.
As usual, docs were cross-checked with official tg api docs.
* Very minor doc fix for passportfile.py and passportelementerrors.py
Didn't bother changing too much since this seems to be a custom implementation.
* Doc fixes for telegram.payments
As usual, cross-checked with official bot api docs.
* Address review 2
Few tiny other fixes too.
* Changed from ``True/False/None`` to :obj:`True/False/None` project-wide.
Few tiny other doc fixes too.
Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com>
Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com>
Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
|
|
|
Stop condition for loop: `self.running` evaluates :obj:`False` or return value of
|
|
|
|
`action_cb` evaluates :obj:`False`.
|
2018-03-02 22:11:16 +01:00
|
|
|
|
|
|
|
Args:
|
|
|
|
action_cb (:obj:`callable`): Network oriented callback function to call.
|
|
|
|
onerr_cb (:obj:`callable`): Callback to call when TelegramError is caught. Receives the
|
|
|
|
exception object as a parameter.
|
|
|
|
description (:obj:`str`): Description text to use for logs and exception raised.
|
|
|
|
interval (:obj:`float` | :obj:`int`): Interval to sleep between each call to
|
|
|
|
`action_cb`.
|
|
|
|
|
|
|
|
"""
|
|
|
|
self.logger.debug('Start network loop retry %s', description)
|
|
|
|
cur_interval = interval
|
2015-11-15 17:36:38 +01:00
|
|
|
while self.running:
|
2015-11-05 13:52:33 +01:00
|
|
|
try:
|
2018-03-02 22:11:16 +01:00
|
|
|
if not action_cb():
|
|
|
|
break
|
2020-10-31 16:33:34 +01:00
|
|
|
except RetryAfter as exc:
|
|
|
|
self.logger.info('%s', exc)
|
|
|
|
cur_interval = 0.5 + exc.retry_after
|
2018-02-18 16:50:38 +01:00
|
|
|
except TimedOut as toe:
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.debug('Timed out %s: %s', description, toe)
|
|
|
|
# If failure is due to timeout, we should retry asap.
|
2018-02-18 16:50:38 +01:00
|
|
|
cur_interval = 0
|
2018-03-02 22:11:16 +01:00
|
|
|
except InvalidToken as pex:
|
|
|
|
self.logger.error('Invalid token; aborting')
|
|
|
|
raise pex
|
2020-10-31 16:33:34 +01:00
|
|
|
except TelegramError as telegram_exc:
|
|
|
|
self.logger.error('Error while %s: %s', description, telegram_exc)
|
|
|
|
onerr_cb(telegram_exc)
|
2016-01-23 23:08:53 +01:00
|
|
|
cur_interval = self._increase_poll_interval(cur_interval)
|
|
|
|
else:
|
2018-03-02 22:11:16 +01:00
|
|
|
cur_interval = interval
|
2016-01-23 23:08:53 +01:00
|
|
|
|
2018-02-18 16:50:38 +01:00
|
|
|
if cur_interval:
|
|
|
|
sleep(cur_interval)
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-01-23 23:08:53 +01:00
|
|
|
@staticmethod
|
2020-10-06 19:28:40 +02:00
|
|
|
def _increase_poll_interval(current_interval: float) -> float:
|
2016-01-23 23:08:53 +01:00
|
|
|
# increase waiting times on subsequent errors up to 30secs
|
|
|
|
if current_interval == 0:
|
|
|
|
current_interval = 1
|
|
|
|
elif current_interval < 30:
|
2021-05-27 09:38:17 +02:00
|
|
|
current_interval *= 1.5
|
|
|
|
else:
|
|
|
|
current_interval = min(30.0, current_interval)
|
2016-01-23 23:08:53 +01:00
|
|
|
return current_interval
|
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
2020-10-09 17:22:07 +02:00
|
|
|
def _start_webhook(
|
|
|
|
self,
|
|
|
|
listen,
|
|
|
|
port,
|
|
|
|
url_path,
|
|
|
|
cert,
|
|
|
|
key,
|
|
|
|
bootstrap_retries,
|
2021-03-13 15:35:26 +01:00
|
|
|
drop_pending_updates,
|
2020-10-09 17:22:07 +02:00
|
|
|
webhook_url,
|
|
|
|
allowed_updates,
|
|
|
|
ready=None,
|
2021-03-13 15:35:26 +01:00
|
|
|
ip_address=None,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections: int = 40,
|
2020-10-09 17:22:07 +02:00
|
|
|
):
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.debug('Updater thread started (webhook)')
|
2021-03-13 15:35:26 +01:00
|
|
|
|
|
|
|
# Note that we only use the SSL certificate for the WebhookServer, if the key is also
|
|
|
|
# present. This is because the WebhookServer may not actually be in charge of performing
|
|
|
|
# the SSL handshake, e.g. in case a reverse proxy is used
|
2015-11-30 23:00:32 +01:00
|
|
|
use_ssl = cert is not None and key is not None
|
2021-03-13 15:35:26 +01:00
|
|
|
|
2016-03-23 10:11:10 +01:00
|
|
|
if not url_path.startswith('/'):
|
2020-11-23 22:09:29 +01:00
|
|
|
url_path = f'/{url_path}'
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2018-09-08 22:25:48 +02:00
|
|
|
# Create Tornado app instance
|
2020-07-19 17:47:26 +02:00
|
|
|
app = WebhookAppClass(url_path, self.bot, self.update_queue)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2018-09-08 22:25:48 +02:00
|
|
|
# Form SSL Context
|
|
|
|
# An SSLError is raised if the private key does not match with the certificate
|
2015-11-30 23:00:32 +01:00
|
|
|
if use_ssl:
|
2018-09-08 22:25:48 +02:00
|
|
|
try:
|
|
|
|
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
|
|
ssl_ctx.load_cert_chain(cert, key)
|
2020-10-31 16:33:34 +01:00
|
|
|
except ssl.SSLError as exc:
|
|
|
|
raise TelegramError('Invalid SSL Certificate') from exc
|
2018-09-08 22:25:48 +02:00
|
|
|
else:
|
|
|
|
ssl_ctx = None
|
|
|
|
|
|
|
|
# Create and start server
|
2019-04-15 09:28:41 +02:00
|
|
|
self.httpd = WebhookServer(listen, port, app, ssl_ctx)
|
2016-03-23 10:11:10 +01:00
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
if not webhook_url:
|
|
|
|
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
|
|
|
|
|
|
|
# We pass along the cert to the webhook if present.
|
2021-05-27 09:38:17 +02:00
|
|
|
cert_file = open(cert, 'rb') if cert is not None else None
|
2021-03-13 15:35:26 +01:00
|
|
|
self._bootstrap(
|
|
|
|
max_retries=bootstrap_retries,
|
|
|
|
drop_pending_updates=drop_pending_updates,
|
|
|
|
webhook_url=webhook_url,
|
|
|
|
allowed_updates=allowed_updates,
|
2021-05-27 09:38:17 +02:00
|
|
|
cert=cert_file,
|
2021-03-13 15:35:26 +01:00
|
|
|
ip_address=ip_address,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections=max_connections,
|
2021-03-13 15:35:26 +01:00
|
|
|
)
|
2021-05-27 09:38:17 +02:00
|
|
|
if cert_file is not None:
|
|
|
|
cert_file.close()
|
2015-11-30 23:00:32 +01:00
|
|
|
|
2021-05-05 20:59:06 +02:00
|
|
|
self.httpd.serve_forever(ready=ready)
|
2016-03-23 10:11:10 +01:00
|
|
|
|
2016-05-10 23:58:55 +02:00
|
|
|
@staticmethod
|
2020-10-06 19:28:40 +02:00
|
|
|
def _gen_webhook_url(listen: str, port: int, url_path: str) -> str:
|
2020-11-23 22:09:29 +01:00
|
|
|
return f'https://{listen}:{port}{url_path}'
|
2016-03-14 00:33:09 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
2020-10-09 17:22:07 +02:00
|
|
|
def _bootstrap(
|
2021-03-13 15:35:26 +01:00
|
|
|
self,
|
|
|
|
max_retries,
|
|
|
|
drop_pending_updates,
|
|
|
|
webhook_url,
|
|
|
|
allowed_updates,
|
|
|
|
cert=None,
|
|
|
|
bootstrap_interval=5,
|
|
|
|
ip_address=None,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections: int = 40,
|
2020-10-09 17:22:07 +02:00
|
|
|
):
|
2018-03-02 22:11:16 +01:00
|
|
|
retries = [0]
|
|
|
|
|
|
|
|
def bootstrap_del_webhook():
|
2021-03-13 15:35:26 +01:00
|
|
|
self.logger.debug('Deleting webhook')
|
|
|
|
if drop_pending_updates:
|
|
|
|
self.logger.debug('Dropping pending updates from Telegram server')
|
|
|
|
self.bot.delete_webhook(drop_pending_updates=drop_pending_updates)
|
2018-03-02 22:11:16 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def bootstrap_set_webhook():
|
2021-03-13 15:35:26 +01:00
|
|
|
self.logger.debug('Setting webhook')
|
|
|
|
if drop_pending_updates:
|
|
|
|
self.logger.debug('Dropping pending updates from Telegram server')
|
2020-10-09 17:22:07 +02:00
|
|
|
self.bot.set_webhook(
|
2021-03-13 15:35:26 +01:00
|
|
|
url=webhook_url,
|
|
|
|
certificate=cert,
|
|
|
|
allowed_updates=allowed_updates,
|
|
|
|
ip_address=ip_address,
|
|
|
|
drop_pending_updates=drop_pending_updates,
|
2021-06-06 09:27:45 +02:00
|
|
|
max_connections=max_connections,
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def bootstrap_onerr_cb(exc):
|
|
|
|
if not isinstance(exc, Unauthorized) and (max_retries < 0 or retries[0] < max_retries):
|
|
|
|
retries[0] += 1
|
2020-10-09 17:22:07 +02:00
|
|
|
self.logger.warning(
|
|
|
|
'Failed bootstrap phase; try=%s max_retries=%s', retries[0], max_retries
|
|
|
|
)
|
2016-05-10 23:58:55 +02:00
|
|
|
else:
|
2018-03-02 22:11:16 +01:00
|
|
|
self.logger.error('Failed bootstrap phase after %s retries (%s)', retries[0], exc)
|
|
|
|
raise exc
|
|
|
|
|
2021-03-13 15:35:26 +01:00
|
|
|
# Dropping pending updates from TG can be efficiently done with the drop_pending_updates
|
|
|
|
# parameter of delete/start_webhook, even in the case of polling. Also we want to make
|
|
|
|
# sure that no webhook is configured in case of polling, so we just always call
|
|
|
|
# delete_webhook for polling
|
|
|
|
if drop_pending_updates or not webhook_url:
|
2020-10-09 17:22:07 +02:00
|
|
|
self._network_loop_retry(
|
|
|
|
bootstrap_del_webhook,
|
|
|
|
bootstrap_onerr_cb,
|
|
|
|
'bootstrap del webhook',
|
|
|
|
bootstrap_interval,
|
|
|
|
)
|
2018-03-02 22:11:16 +01:00
|
|
|
retries[0] = 0
|
|
|
|
|
|
|
|
# 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:
|
2020-10-09 17:22:07 +02:00
|
|
|
self._network_loop_retry(
|
|
|
|
bootstrap_set_webhook,
|
|
|
|
bootstrap_onerr_cb,
|
|
|
|
'bootstrap set webhook',
|
|
|
|
bootstrap_interval,
|
|
|
|
)
|
2016-03-01 20:40:04 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
def stop(self) -> None:
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
|
2016-01-05 13:32:19 +01:00
|
|
|
self.job_queue.stop()
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
2016-09-06 15:38:07 +02:00
|
|
|
if self.running or self.dispatcher.has_running_threads:
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Stopping Updater and Dispatcher...')
|
2016-02-09 22:08:27 +01:00
|
|
|
|
|
|
|
self.running = False
|
|
|
|
|
|
|
|
self._stop_httpd()
|
|
|
|
self._stop_dispatcher()
|
|
|
|
self._join_threads()
|
2016-09-06 15:38:07 +02:00
|
|
|
|
|
|
|
# Stop the Request instance only if it was created by the Updater
|
|
|
|
if self._request:
|
|
|
|
self._request.stop()
|
2016-02-09 22:08:27 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
|
|
|
def _stop_httpd(self) -> None:
|
2016-02-09 22:08:27 +01:00
|
|
|
if self.httpd:
|
2020-10-09 17:22:07 +02:00
|
|
|
self.logger.debug(
|
|
|
|
'Waiting for current webhook connection to be '
|
|
|
|
'closed... Send a Telegram message to the bot to exit '
|
|
|
|
'immediately.'
|
|
|
|
)
|
2016-02-09 22:08:27 +01:00
|
|
|
self.httpd.shutdown()
|
|
|
|
self.httpd = None
|
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
|
|
|
def _stop_dispatcher(self) -> None:
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Requesting Dispatcher to stop...')
|
2016-02-09 22:08:27 +01:00
|
|
|
self.dispatcher.stop()
|
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
|
|
|
def _join_threads(self) -> None:
|
2016-02-09 22:08:27 +01:00
|
|
|
for thr in self.__threads:
|
2020-10-31 16:33:34 +01:00
|
|
|
self.logger.debug('Waiting for %s thread to end', thr.name)
|
2016-02-09 22:08:27 +01:00
|
|
|
thr.join()
|
2020-10-31 16:33:34 +01:00
|
|
|
self.logger.debug('%s thread has ended', thr.name)
|
2016-02-09 22:08:27 +01:00
|
|
|
self.__threads = []
|
2015-11-22 19:15:17 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
@no_type_check
|
2021-05-27 09:38:17 +02:00
|
|
|
def _signal_handler(self, signum, frame) -> None:
|
2015-11-23 03:45:47 +01:00
|
|
|
self.is_idle = False
|
2016-04-18 19:21:57 +02:00
|
|
|
if self.running:
|
2020-10-09 17:22:07 +02:00
|
|
|
self.logger.info(
|
2020-10-31 16:33:34 +01:00
|
|
|
'Received signal %s (%s), stopping...', signum, get_signal_name(signum)
|
2020-10-09 17:22:07 +02:00
|
|
|
)
|
2018-09-20 22:50:40 +02:00
|
|
|
if self.persistence:
|
Documentation Improvements (#2008)
* Minor doc updates, following official API docs
* Fix spelling in Defaults docstrings
* Clarify Changelog of v12.7 about aware dates
* Fix typo in CHANGES.rst (#2024)
* Fix PicklePersistence.flush() with only bot_data (#2017)
* Update pylint in pre-commit to fix CI (#2018)
* Add Filters.via_bot (#2009)
* feat: via_bot filter
also fixing a small mistake in the empty parameter of the user filter and improve docs slightly
* fix: forgot to set via_bot to None
* fix: redoing subclassing to copy paste solution
* Cosmetic changes
Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
* Update CHANGES.rst
Fixed Typo
Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
* Update downloads badge, add info on IRC Channel to Getting Help section
* Remove RegexHandler from ConversationHandlers Docs (#1973)
Replaced RegexHandler with MessageHandler, since the former is deprecated
* Fix Filters.via_bot docstrings
* Add notes on Markdown v1 being legacy mode
* Fixed typo in the Regex doc.. (#2036)
* Typo: Spelling
* Minor cleanup from #2043
* Document CommandHandler ignoring channel posts
* Doc fixes for a few telegram.ext classes
* Doc fixes for most `telegram` classes.
* pep-8
forgot the hard wrap is at 99 chars, not 100!
fixed a few spelling mistakes too.
* Address review and made rendering of booleans consistent
True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes.
* Few doc fixes for inline* classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram/files classes
As usual, docs were cross-checked with official tg api docs.
* Doc fixes for telegram.Game
Mostly just added hyperlinks. And fixed message length doc.
As usual, docs were cross-checked with official tg api docs.
* Very minor doc fix for passportfile.py and passportelementerrors.py
Didn't bother changing too much since this seems to be a custom implementation.
* Doc fixes for telegram.payments
As usual, cross-checked with official bot api docs.
* Address review 2
Few tiny other fixes too.
* Changed from ``True/False/None`` to :obj:`True/False/None` project-wide.
Few tiny other doc fixes too.
Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com>
Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com>
Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
|
|
|
# Update user_data, chat_data and bot_data before flushing
|
2019-02-13 23:30:29 +01:00
|
|
|
self.dispatcher.update_persistence()
|
2018-09-20 22:50:40 +02:00
|
|
|
self.persistence.flush()
|
2016-04-18 19:21:57 +02:00
|
|
|
self.stop()
|
2017-03-26 14:36:13 +02:00
|
|
|
if self.user_sig_handler:
|
|
|
|
self.user_sig_handler(signum, frame)
|
2016-04-18 19:21:57 +02:00
|
|
|
else:
|
|
|
|
self.logger.warning('Exiting immediately!')
|
2020-10-31 16:33:34 +01:00
|
|
|
# pylint: disable=C0415,W0212
|
2016-04-18 19:21:57 +02:00
|
|
|
import os
|
2020-10-09 17:22:07 +02:00
|
|
|
|
2016-04-18 19:21:57 +02:00
|
|
|
os._exit(1)
|
2015-11-23 03:45:47 +01:00
|
|
|
|
2020-10-06 19:28:40 +02:00
|
|
|
def idle(self, stop_signals: Union[List, Tuple] = (SIGINT, SIGTERM, SIGABRT)) -> None:
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Blocks until one of the signals are received and stops the updater.
|
2015-11-23 17:40:39 +01:00
|
|
|
|
|
|
|
Args:
|
2020-04-08 22:49:01 +02:00
|
|
|
stop_signals (:obj:`list` | :obj:`tuple`): List containing signals from the signal
|
2021-04-30 10:47:41 +02:00
|
|
|
module that should be subscribed to. :meth:`Updater.stop()` will be called on
|
|
|
|
receiving one of those signals. Defaults to (``SIGINT``, ``SIGTERM``, ``SIGABRT``).
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2015-11-23 17:40:39 +01:00
|
|
|
for sig in stop_signals:
|
2021-05-27 09:38:17 +02:00
|
|
|
signal(sig, self._signal_handler)
|
2015-11-23 03:45:47 +01:00
|
|
|
|
|
|
|
self.is_idle = True
|
|
|
|
|
|
|
|
while self.is_idle:
|
|
|
|
sleep(1)
|