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
|
2020-02-02 22:08:54 +01:00
|
|
|
# Copyright (C) 2015-2020
|
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/].
|
2016-01-13 17:09:35 +01:00
|
|
|
"""This module contains the Dispatcher class."""
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
import logging
|
2018-09-21 08:57:01 +02:00
|
|
|
import warnings
|
2016-09-06 15:38:07 +02:00
|
|
|
import weakref
|
2015-11-11 13:33:03 +01:00
|
|
|
from functools import wraps
|
2016-09-06 15:38:07 +02:00
|
|
|
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
|
2016-01-06 15:35:55 +01:00
|
|
|
from time import sleep
|
2016-09-06 15:38:07 +02:00
|
|
|
from uuid import uuid4
|
2016-10-25 19:28:34 +02:00
|
|
|
from collections import defaultdict
|
2016-09-06 15:38:07 +02:00
|
|
|
|
2016-05-30 03:16:33 +02:00
|
|
|
from queue import Queue, Empty
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-06-01 20:21:24 +02:00
|
|
|
from future.builtins import range
|
2016-04-16 19:25:38 +02:00
|
|
|
|
2018-11-09 11:44:20 +01:00
|
|
|
from telegram import TelegramError, Update
|
2016-04-14 23:57:40 +02:00
|
|
|
from telegram.ext.handler import Handler
|
2018-09-21 08:57:01 +02:00
|
|
|
from telegram.ext.callbackcontext import CallbackContext
|
|
|
|
from telegram.utils.deprecate import TelegramDeprecationWarning
|
2016-07-15 01:30:54 +02:00
|
|
|
from telegram.utils.promise import Promise
|
2018-09-20 22:50:40 +02:00
|
|
|
from telegram.ext import BasePersistence
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-08-20 22:01:07 +02:00
|
|
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
2016-04-14 23:57:40 +02:00
|
|
|
DEFAULT_GROUP = 0
|
2015-11-06 00:24:01 +01:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
|
|
|
|
def run_async(func):
|
2019-02-13 12:18:37 +01:00
|
|
|
"""
|
|
|
|
Function decorator that will run the function in a new thread.
|
2017-09-01 08:43:08 +02:00
|
|
|
|
|
|
|
Will run :attr:`telegram.ext.Dispatcher.run_async`.
|
|
|
|
|
2016-09-06 15:38:07 +02:00
|
|
|
Using this decorator is only possible when only a single Dispatcher exist in the system.
|
2015-11-11 13:33:03 +01:00
|
|
|
|
2019-02-13 12:18:37 +01:00
|
|
|
Warning:
|
|
|
|
If you're using @run_async you cannot rely on adding custom attributes to
|
2019-02-18 20:04:52 +01:00
|
|
|
:class:`telegram.ext.CallbackContext`. See its docs for more info.
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2018-09-20 22:50:40 +02:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
@wraps(func)
|
2016-06-01 20:11:00 +02:00
|
|
|
def async_func(*args, **kwargs):
|
2016-09-06 15:38:07 +02:00
|
|
|
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
|
2015-11-11 13:33:03 +01:00
|
|
|
|
|
|
|
return async_func
|
|
|
|
|
|
|
|
|
2017-08-12 17:57:12 +02:00
|
|
|
class DispatcherHandlerStop(Exception):
|
|
|
|
"""Raise this in handler to prevent execution any other handler (even in different group)."""
|
2017-07-29 19:15:43 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2016-04-15 16:20:37 +02:00
|
|
|
class Dispatcher(object):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""This class dispatches all kinds of updates to its registered handlers.
|
2015-12-21 20:25:31 +01:00
|
|
|
|
2017-07-23 22:33:08 +02:00
|
|
|
Attributes:
|
|
|
|
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
|
|
|
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
|
|
|
job_queue (:class:`telegram.ext.JobQueue`): Optional. The :class:`telegram.ext.JobQueue`
|
|
|
|
instance to pass onto handler callbacks.
|
|
|
|
workers (:obj:`int`): Number of maximum concurrent worker threads for the ``@run_async``
|
|
|
|
decorator.
|
2018-09-20 22:50:40 +02:00
|
|
|
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
|
|
|
|
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
|
2020-02-02 22:20:31 +01:00
|
|
|
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
|
2018-09-20 22:50:40 +02:00
|
|
|
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
|
|
|
store data that should be persistent over restarts
|
2016-09-06 15:38:07 +02:00
|
|
|
|
2017-07-23 22:33:08 +02:00
|
|
|
Args:
|
|
|
|
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
|
|
|
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
|
|
|
job_queue (:class:`telegram.ext.JobQueue`, optional): The :class:`telegram.ext.JobQueue`
|
|
|
|
instance to pass onto handler callbacks.
|
|
|
|
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
|
|
|
|
``@run_async`` decorator. defaults to 4.
|
2018-09-20 22:50:40 +02:00
|
|
|
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
|
|
|
store data that should be persistent over restarts
|
2018-09-21 08:57:01 +02:00
|
|
|
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API.
|
|
|
|
During the deprecation period of the old API the default is ``False``. **New users**:
|
|
|
|
set this to ``True``.
|
2017-09-01 08:43:08 +02:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2017-07-23 22:33:08 +02:00
|
|
|
|
2016-09-06 15:38:07 +02:00
|
|
|
__singleton_lock = Lock()
|
|
|
|
__singleton_semaphore = BoundedSemaphore()
|
|
|
|
__singleton = None
|
|
|
|
logger = logging.getLogger(__name__)
|
2016-05-15 03:46:40 +02:00
|
|
|
|
2018-09-21 08:57:01 +02:00
|
|
|
def __init__(self,
|
|
|
|
bot,
|
|
|
|
update_queue,
|
|
|
|
workers=4,
|
|
|
|
exception_event=None,
|
|
|
|
job_queue=None,
|
|
|
|
persistence=None,
|
|
|
|
use_context=False):
|
2015-11-05 13:52:33 +01:00
|
|
|
self.bot = bot
|
|
|
|
self.update_queue = update_queue
|
2018-09-21 08:57:01 +02:00
|
|
|
self.job_queue = job_queue
|
2016-09-27 09:21:35 +02:00
|
|
|
self.workers = workers
|
2018-09-21 08:57:01 +02:00
|
|
|
self.use_context = use_context
|
|
|
|
|
|
|
|
if not use_context:
|
2019-02-13 12:07:25 +01:00
|
|
|
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
|
2018-09-21 08:57:01 +02:00
|
|
|
TelegramDeprecationWarning, stacklevel=3)
|
|
|
|
|
2016-10-25 19:28:34 +02:00
|
|
|
self.user_data = defaultdict(dict)
|
|
|
|
self.chat_data = defaultdict(dict)
|
2020-02-02 22:20:31 +01:00
|
|
|
self.bot_data = {}
|
2018-09-20 22:50:40 +02:00
|
|
|
if persistence:
|
|
|
|
if not isinstance(persistence, BasePersistence):
|
|
|
|
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
|
|
|
|
self.persistence = persistence
|
|
|
|
if self.persistence.store_user_data:
|
|
|
|
self.user_data = self.persistence.get_user_data()
|
|
|
|
if not isinstance(self.user_data, defaultdict):
|
|
|
|
raise ValueError("user_data must be of type defaultdict")
|
|
|
|
if self.persistence.store_chat_data:
|
|
|
|
self.chat_data = self.persistence.get_chat_data()
|
|
|
|
if not isinstance(self.chat_data, defaultdict):
|
|
|
|
raise ValueError("chat_data must be of type defaultdict")
|
2020-02-02 22:20:31 +01:00
|
|
|
if self.persistence.store_bot_data:
|
|
|
|
self.bot_data = self.persistence.get_bot_data()
|
|
|
|
if not isinstance(self.bot_data, dict):
|
|
|
|
raise ValueError("bot_data must be of type dict")
|
2018-09-20 22:50:40 +02:00
|
|
|
else:
|
|
|
|
self.persistence = None
|
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.handlers = {}
|
2017-07-23 22:33:08 +02:00
|
|
|
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
2016-04-25 09:18:26 +02:00
|
|
|
self.groups = []
|
2017-07-23 22:33:08 +02:00
|
|
|
"""List[:obj:`int`]: A list with all groups."""
|
2015-11-05 13:52:33 +01:00
|
|
|
self.error_handlers = []
|
2017-07-23 22:33:08 +02:00
|
|
|
"""List[:obj:`callable`]: A list of errorHandlers."""
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
self.running = False
|
2017-07-23 22:33:08 +02:00
|
|
|
""":obj:`bool`: Indicates if this dispatcher is running."""
|
2016-02-06 09:10:08 +01:00
|
|
|
self.__stop_event = Event()
|
2016-02-06 11:38:56 +01:00
|
|
|
self.__exception_event = exception_event or Event()
|
2016-09-06 15:38:07 +02:00
|
|
|
self.__async_queue = Queue()
|
|
|
|
self.__async_threads = set()
|
|
|
|
|
|
|
|
# For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's
|
|
|
|
# only one instance of Dispatcher, it will be possible to use the `run_async` decorator.
|
|
|
|
with self.__singleton_lock:
|
|
|
|
if self.__singleton_semaphore.acquire(blocking=0):
|
|
|
|
self._set_singleton(self)
|
2016-06-19 23:46:34 +02:00
|
|
|
else:
|
2016-09-06 15:38:07 +02:00
|
|
|
self._set_singleton(None)
|
|
|
|
|
2020-01-26 20:59:47 +01:00
|
|
|
@property
|
|
|
|
def exception_event(self):
|
|
|
|
return self.__exception_event
|
|
|
|
|
2016-09-06 15:38:07 +02:00
|
|
|
def _init_async_threads(self, base_name, workers):
|
|
|
|
base_name = '{}_'.format(base_name) if base_name else ''
|
|
|
|
|
|
|
|
for i in range(workers):
|
2019-08-23 22:57:08 +02:00
|
|
|
thread = Thread(target=self._pooled, name='Bot:{}:worker:{}{}'.format(self.bot.id,
|
|
|
|
base_name, i))
|
2016-09-06 15:38:07 +02:00
|
|
|
self.__async_threads.add(thread)
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _set_singleton(cls, val):
|
|
|
|
cls.logger.debug('Setting singleton dispatcher as %s', val)
|
|
|
|
cls.__singleton = weakref.ref(val) if val else None
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_instance(cls):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Get the singleton instance of this class.
|
2016-09-06 15:38:07 +02:00
|
|
|
|
|
|
|
Returns:
|
2017-07-23 22:33:08 +02:00
|
|
|
:class:`telegram.ext.Dispatcher`
|
2016-09-06 15:38:07 +02:00
|
|
|
|
2017-07-23 22:33:08 +02:00
|
|
|
Raises:
|
|
|
|
RuntimeError
|
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2016-09-06 15:38:07 +02:00
|
|
|
if cls.__singleton is not None:
|
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
|
|
|
return cls.__singleton() # pylint: disable=not-callable
|
2016-09-06 15:38:07 +02:00
|
|
|
else:
|
|
|
|
raise RuntimeError('{} not initialized or multiple instances exist'.format(
|
|
|
|
cls.__name__))
|
|
|
|
|
|
|
|
def _pooled(self):
|
|
|
|
thr_name = current_thread().getName()
|
|
|
|
while 1:
|
|
|
|
promise = self.__async_queue.get()
|
|
|
|
|
|
|
|
# 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))
|
|
|
|
break
|
|
|
|
|
2017-02-26 22:27:03 +01:00
|
|
|
promise.run()
|
2017-08-12 17:57:12 +02:00
|
|
|
if isinstance(promise.exception, DispatcherHandlerStop):
|
|
|
|
self.logger.warning(
|
|
|
|
'DispatcherHandlerStop is not supported with async functions; func: %s',
|
|
|
|
promise.pooled_function.__name__)
|
2016-09-06 15:38:07 +02:00
|
|
|
|
|
|
|
def run_async(self, func, *args, **kwargs):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Queue a function (with given args/kwargs) to be run asynchronously.
|
2016-09-06 15:38:07 +02:00
|
|
|
|
2019-02-13 12:18:37 +01:00
|
|
|
Warning:
|
|
|
|
If you're using @run_async you cannot rely on adding custom attributes to
|
2019-02-18 20:04:52 +01:00
|
|
|
:class:`telegram.ext.CallbackContext`. See its docs for more info.
|
2019-02-13 12:18:37 +01:00
|
|
|
|
2016-09-06 15:38:07 +02:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
func (:obj:`callable`): The function to run in the thread.
|
|
|
|
*args (:obj:`tuple`, optional): Arguments to `func`.
|
|
|
|
**kwargs (:obj:`dict`, optional): Keyword arguments to `func`.
|
2016-09-06 15:38:07 +02:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Promise
|
|
|
|
|
|
|
|
"""
|
|
|
|
# TODO: handle exception in async threads
|
|
|
|
# set a threading.Event to notify caller thread
|
|
|
|
promise = Promise(func, args, kwargs)
|
|
|
|
self.__async_queue.put(promise)
|
|
|
|
return promise
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2017-10-21 13:40:24 +02:00
|
|
|
def start(self, ready=None):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Thread target of thread 'dispatcher'.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
Runs in background and processes the update queue.
|
|
|
|
|
2017-10-21 13:40:24 +02:00
|
|
|
Args:
|
|
|
|
ready (:obj:`threading.Event`, optional): If specified, the event will be set once the
|
|
|
|
dispatcher is ready.
|
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2016-02-06 09:10:08 +01:00
|
|
|
if self.running:
|
|
|
|
self.logger.warning('already running')
|
2017-10-21 13:40:24 +02:00
|
|
|
if ready is not None:
|
|
|
|
ready.set()
|
2016-02-06 09:10:08 +01:00
|
|
|
return
|
|
|
|
|
2016-02-06 11:38:56 +01:00
|
|
|
if self.__exception_event.is_set():
|
|
|
|
msg = 'reusing dispatcher after exception event is forbidden'
|
|
|
|
self.logger.error(msg)
|
|
|
|
raise TelegramError(msg)
|
|
|
|
|
2016-09-27 09:21:35 +02:00
|
|
|
self._init_async_threads(uuid4(), self.workers)
|
2016-02-06 09:10:08 +01:00
|
|
|
self.running = True
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Dispatcher started')
|
2016-02-06 09:10:08 +01:00
|
|
|
|
2017-10-21 13:40:24 +02:00
|
|
|
if ready is not None:
|
|
|
|
ready.set()
|
|
|
|
|
2016-06-19 23:46:34 +02:00
|
|
|
while 1:
|
2016-02-06 09:10:08 +01:00
|
|
|
try:
|
|
|
|
# Pop update from update queue.
|
2016-04-14 23:57:40 +02:00
|
|
|
update = self.update_queue.get(True, 1)
|
2016-02-06 09:10:08 +01:00
|
|
|
except Empty:
|
|
|
|
if self.__stop_event.is_set():
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('orderly stopping')
|
2016-02-06 11:38:56 +01:00
|
|
|
break
|
2016-04-14 22:23:02 +02:00
|
|
|
elif self.__exception_event.is_set():
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.critical('stopping due to exception in another thread')
|
2016-02-06 09:10:08 +01:00
|
|
|
break
|
|
|
|
continue
|
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.logger.debug('Processing Update: %s' % update)
|
2016-06-19 23:46:34 +02:00
|
|
|
self.process_update(update)
|
2019-08-23 20:54:07 +02:00
|
|
|
self.update_queue.task_done()
|
2016-02-06 09:10:08 +01:00
|
|
|
|
|
|
|
self.running = False
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Dispatcher thread stopped')
|
2015-11-15 17:36:38 +01:00
|
|
|
|
|
|
|
def stop(self):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Stops the thread."""
|
2016-02-06 09:10:08 +01:00
|
|
|
if self.running:
|
|
|
|
self.__stop_event.set()
|
|
|
|
while self.running:
|
|
|
|
sleep(0.1)
|
|
|
|
self.__stop_event.clear()
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-09-06 15:38:07 +02:00
|
|
|
# async threads must be join()ed only after the dispatcher thread was joined,
|
|
|
|
# otherwise we can still have new async threads dispatched
|
|
|
|
threads = list(self.__async_threads)
|
|
|
|
total = len(threads)
|
|
|
|
|
|
|
|
# Stop all threads in the thread pool by put()ting one non-tuple per thread
|
|
|
|
for i in range(total):
|
|
|
|
self.__async_queue.put(None)
|
|
|
|
|
|
|
|
for i, thr in enumerate(threads):
|
|
|
|
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
|
|
|
|
thr.join()
|
|
|
|
self.__async_threads.remove(thr)
|
|
|
|
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def has_running_threads(self):
|
|
|
|
return self.running or bool(self.__async_threads)
|
|
|
|
|
2016-06-19 23:46:34 +02:00
|
|
|
def process_update(self, update):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Processes a single update.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
|
|
|
Args:
|
2017-07-29 19:15:43 +02:00
|
|
|
update (:obj:`str` | :class:`telegram.Update` | :class:`telegram.TelegramError`):
|
|
|
|
The update to process.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2019-08-23 20:38:29 +02:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
# An error happened while polling
|
|
|
|
if isinstance(update, TelegramError):
|
2017-09-01 08:46:21 +02:00
|
|
|
try:
|
|
|
|
self.dispatch_error(None, update)
|
|
|
|
except Exception:
|
|
|
|
self.logger.exception('An uncaught error was raised while handling the error')
|
2017-07-29 19:15:43 +02:00
|
|
|
return
|
2017-09-01 08:46:21 +02:00
|
|
|
|
2019-02-13 12:18:37 +01:00
|
|
|
context = None
|
|
|
|
|
2017-07-29 19:15:43 +02:00
|
|
|
for group in self.groups:
|
|
|
|
try:
|
2018-09-21 08:57:01 +02:00
|
|
|
for handler in self.handlers[group]:
|
|
|
|
check = handler.check_update(update)
|
|
|
|
if check is not None and check is not False:
|
2019-02-13 12:18:37 +01:00
|
|
|
if not context and self.use_context:
|
|
|
|
context = CallbackContext.from_update(update, self)
|
|
|
|
handler.handle_update(update, self, check, context)
|
2020-04-10 13:23:13 +02:00
|
|
|
self.update_persistence(update=update)
|
2018-09-21 08:57:01 +02:00
|
|
|
break
|
2017-08-12 17:57:12 +02:00
|
|
|
|
|
|
|
# Stop processing with any other handler.
|
2017-07-29 19:15:43 +02:00
|
|
|
except DispatcherHandlerStop:
|
2017-08-12 17:57:12 +02:00
|
|
|
self.logger.debug('Stopping further handlers due to DispatcherHandlerStop')
|
2020-04-10 13:23:13 +02:00
|
|
|
self.update_persistence(update=update)
|
2017-07-29 19:15:43 +02:00
|
|
|
break
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2017-08-12 17:57:12 +02:00
|
|
|
# Dispatch any error.
|
2019-08-27 09:09:02 +02:00
|
|
|
except Exception as e:
|
2017-08-12 17:57:12 +02:00
|
|
|
try:
|
2019-08-27 09:09:02 +02:00
|
|
|
self.dispatch_error(update, e)
|
2017-08-12 17:57:12 +02:00
|
|
|
except DispatcherHandlerStop:
|
|
|
|
self.logger.debug('Error handler stopped further handlers')
|
|
|
|
break
|
2019-08-27 09:09:02 +02:00
|
|
|
# Errors should not stop the thread.
|
2017-08-12 17:57:12 +02:00
|
|
|
except Exception:
|
2019-08-27 09:09:02 +02:00
|
|
|
self.logger.exception('An error was raised while processing the update and an '
|
|
|
|
'uncaught error was raised while handling the error '
|
|
|
|
'with an error_handler')
|
2017-08-12 17:57:12 +02:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def add_handler(self, handler, group=DEFAULT_GROUP):
|
2017-08-12 17:57:12 +02:00
|
|
|
"""Register a handler.
|
2016-04-25 09:45:55 +02:00
|
|
|
|
2020-02-02 20:20:54 +01:00
|
|
|
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
|
|
|
|
update with :class:`telegram.ext.DispatcherHandlerStop`.
|
2016-04-25 09:45:55 +02:00
|
|
|
|
2017-07-23 22:33:08 +02:00
|
|
|
A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers
|
|
|
|
are organized in groups with a numeric value. The default group is 0. All groups will be
|
2017-08-12 17:57:12 +02:00
|
|
|
evaluated for handling an update, but only 0 or 1 handler per group will be used. If
|
2018-04-17 06:42:09 +02:00
|
|
|
:class:`telegram.ext.DispatcherHandlerStop` is raised from one of the handlers, no further
|
2017-08-12 17:57:12 +02:00
|
|
|
handlers (regardless of the group) will be called.
|
2016-04-25 09:45:55 +02:00
|
|
|
|
|
|
|
The priority/order of handlers is determined as follows:
|
|
|
|
|
|
|
|
* Priority of the group (lower group number == higher priority)
|
2017-08-12 17:57:12 +02:00
|
|
|
* The first handler in a group which should handle an update (see
|
2017-09-01 10:38:04 +02:00
|
|
|
:attr:`telegram.ext.Handler.check_update`) will be used. Other handlers from the
|
2017-08-12 17:57:12 +02:00
|
|
|
group will not be used. The order in which handlers were added to the group defines the
|
|
|
|
priority.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
handler (:class:`telegram.ext.Handler`): A Handler instance.
|
|
|
|
group (:obj:`int`, optional): The group identifier. Default is 0.
|
2017-08-12 17:57:12 +02:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2018-09-20 22:50:40 +02:00
|
|
|
# Unfortunately due to circular imports this has to be here
|
|
|
|
from .conversationhandler import ConversationHandler
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
if not isinstance(handler, Handler):
|
2016-05-15 03:46:40 +02:00
|
|
|
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
|
2016-04-25 09:44:55 +02:00
|
|
|
if not isinstance(group, int):
|
|
|
|
raise TypeError('group is not int')
|
2018-09-20 22:50:40 +02:00
|
|
|
if isinstance(handler, ConversationHandler) and handler.persistent:
|
|
|
|
if not self.persistence:
|
|
|
|
raise ValueError(
|
|
|
|
"Conversationhandler {} can not be persistent if dispatcher has no "
|
|
|
|
"persistence".format(handler.name))
|
|
|
|
handler.persistence = self.persistence
|
2020-02-02 22:31:56 +01:00
|
|
|
handler.conversations = self.persistence.get_conversations(handler.name)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
if group not in self.handlers:
|
|
|
|
self.handlers[group] = list()
|
2016-04-25 09:18:26 +02:00
|
|
|
self.groups.append(group)
|
|
|
|
self.groups = sorted(self.groups)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.handlers[group].append(handler)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def remove_handler(self, handler, group=DEFAULT_GROUP):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Remove a handler from the specified group.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2017-07-23 22:33:08 +02:00
|
|
|
handler (:class:`telegram.ext.Handler`): A Handler instance.
|
|
|
|
group (:obj:`object`, optional): The group identifier. Default is 0.
|
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
if handler in self.handlers[group]:
|
|
|
|
self.handlers[group].remove(handler)
|
2016-04-25 09:18:45 +02:00
|
|
|
if not self.handlers[group]:
|
|
|
|
del self.handlers[group]
|
|
|
|
self.groups.remove(group)
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2020-04-10 13:23:13 +02:00
|
|
|
def update_persistence(self, update=None):
|
2020-02-02 22:20:31 +01:00
|
|
|
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
|
2020-04-10 13:23:13 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
|
|
|
|
corresponding ``user_data`` and ``chat_data`` will be updated.
|
2019-02-13 23:30:29 +01:00
|
|
|
"""
|
|
|
|
if self.persistence:
|
2020-04-10 13:23:13 +02:00
|
|
|
chat_ids = self.chat_data.keys()
|
|
|
|
user_ids = self.user_data.keys()
|
|
|
|
|
|
|
|
if isinstance(update, Update):
|
|
|
|
if update.effective_chat:
|
|
|
|
chat_ids = [update.effective_chat.id]
|
|
|
|
else:
|
|
|
|
chat_ids = []
|
|
|
|
if update.effective_user:
|
|
|
|
user_ids = [update.effective_user.id]
|
|
|
|
else:
|
|
|
|
user_ids = []
|
|
|
|
|
2020-02-02 22:20:31 +01:00
|
|
|
if self.persistence.store_bot_data:
|
2020-04-10 13:23:13 +02:00
|
|
|
try:
|
|
|
|
self.persistence.update_bot_data(self.bot_data)
|
|
|
|
except Exception as e:
|
|
|
|
try:
|
|
|
|
self.dispatch_error(update, e)
|
|
|
|
except Exception:
|
|
|
|
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)
|
2019-08-23 20:52:14 +02:00
|
|
|
if self.persistence.store_chat_data:
|
2020-04-10 13:23:13 +02:00
|
|
|
for chat_id in chat_ids:
|
|
|
|
try:
|
|
|
|
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
|
|
|
except Exception as e:
|
|
|
|
try:
|
|
|
|
self.dispatch_error(update, e)
|
|
|
|
except Exception:
|
|
|
|
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)
|
2019-08-23 20:52:14 +02:00
|
|
|
if self.persistence.store_user_data:
|
2020-04-10 13:23:13 +02:00
|
|
|
for user_id in user_ids:
|
|
|
|
try:
|
|
|
|
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
|
|
|
except Exception as e:
|
|
|
|
try:
|
|
|
|
self.dispatch_error(update, e)
|
|
|
|
except Exception:
|
|
|
|
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)
|
2019-02-13 23:30:29 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def add_error_handler(self, callback):
|
2019-08-27 09:09:02 +02:00
|
|
|
"""Registers an error handler in the Dispatcher. This handler will receive every error
|
|
|
|
which happens in your bot.
|
|
|
|
|
|
|
|
Warning: The errors handled within these handlers won't show up in the logger, so you
|
|
|
|
need to make sure that you reraise the error.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2018-09-21 08:57:01 +02:00
|
|
|
callback (:obj:`callable`): The callback function for this error handler. Will be
|
2019-08-27 09:09:02 +02:00
|
|
|
called when an error is raised. Callback signature for context based API:
|
2018-09-21 08:57:01 +02:00
|
|
|
|
|
|
|
``def callback(update: Update, context: CallbackContext)``
|
|
|
|
|
|
|
|
The error that happened will be present in context.error.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2018-09-21 08:57:01 +02:00
|
|
|
Note:
|
2019-02-13 12:07:25 +01:00
|
|
|
See https://git.io/fxJuV for more info about switching to context based API.
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
self.error_handlers.append(callback)
|
2016-01-04 17:31:06 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def remove_error_handler(self, callback):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Removes an error handler.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
|
|
|
Args:
|
2017-07-29 19:15:43 +02:00
|
|
|
callback (:obj:`callable`): The error handler to remove.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
if callback in self.error_handlers:
|
|
|
|
self.error_handlers.remove(callback)
|
2016-01-04 17:31:06 +01:00
|
|
|
|
2016-06-19 23:46:34 +02:00
|
|
|
def dispatch_error(self, update, error):
|
2017-09-01 08:43:08 +02:00
|
|
|
"""Dispatches an error.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
|
|
|
Args:
|
2017-07-29 19:15:43 +02:00
|
|
|
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
|
2019-08-27 09:09:02 +02:00
|
|
|
error (:obj:`Exception`): The error that was raised.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2017-09-01 08:43:08 +02:00
|
|
|
"""
|
2017-10-21 11:35:11 +02:00
|
|
|
if self.error_handlers:
|
|
|
|
for callback in self.error_handlers:
|
2018-09-21 08:57:01 +02:00
|
|
|
if self.use_context:
|
|
|
|
callback(update, CallbackContext.from_error(update, error, self))
|
|
|
|
else:
|
|
|
|
callback(self.bot, update, error)
|
2017-10-21 11:35:11 +02:00
|
|
|
|
|
|
|
else:
|
|
|
|
self.logger.exception(
|
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
|
|
|
'No error handlers are registered, logging exception.', exc_info=error)
|