mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-03-14 19:48:57 +01:00
Merge remote-tracking branch 'origin/master' into urllib3-vendor-beta
This commit is contained in:
commit
a6b28b022a
27 changed files with 698 additions and 134 deletions
6
.github/CONTRIBUTING.rst
vendored
6
.github/CONTRIBUTING.rst
vendored
|
@ -85,6 +85,12 @@ Here's how to make a one-off code change.
|
|||
|
||||
$ make test
|
||||
|
||||
If you don't have ``make``, do:
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ nosetests -v
|
||||
|
||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
@ -19,6 +19,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Anton Tagunov <https://github.com/anton-tagunov>`_
|
||||
- `Balduro <https://github.com/Balduro>`_
|
||||
- `bimmlerd <https://github.com/bimmlerd>`_
|
||||
- `d-qoi <https://github.com/d-qoi>`_
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
|
||||
|
@ -28,6 +29,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Jacob Bom <https://github.com/bomjacob>`_
|
||||
- `JASON0916 <https://github.com/JASON0916>`_
|
||||
- `jh0ker <https://github.com/jh0ker>`_
|
||||
- `John Yong <https://github.com/whipermr5>`_
|
||||
- `jossalgon <https://github.com/jossalgon>`_
|
||||
- `JRoot3D <https://github.com/JRoot3D>`_
|
||||
- `jlmadurga <https://github.com/jlmadurga>`_
|
||||
|
@ -36,6 +38,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `macrojames <https://github.com/macrojames>`_
|
||||
- `Michael Elovskikh <https://github.com/wronglink>`_
|
||||
- `naveenvhegde <https://github.com/naveenvhegde>`_
|
||||
- `neurrone <https://github.com/neurrone>`_
|
||||
- `njittam <https://github.com/njittam>`_
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
|
@ -46,6 +49,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Joscha Götzer <https://github.com/Rostgnom>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `thodnev <https://github.com/thodnev>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
- `voider1 <https://github.com/voider1>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
Changes
|
||||
=======
|
||||
|
||||
**2017-04-17**
|
||||
|
||||
*Released 5.3.1*
|
||||
|
||||
- Hotfix release due to bug introduced by urllib3 version 1.21
|
||||
|
||||
**2016-12-11**
|
||||
|
||||
*Released 5.3*
|
||||
|
|
|
@ -61,7 +61,7 @@ author = u'Leandro Toledo'
|
|||
# The short X.Y version.
|
||||
version = '5.3' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '5.3.0' # telegram.__version__
|
||||
release = '5.3.1' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
7
docs/source/telegram.ext.messagequeue.rst
Normal file
7
docs/source/telegram.ext.messagequeue.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
telegram.ext.messagequeue module
|
||||
================================
|
||||
|
||||
.. automodule:: telegram.ext.messagequeue
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -16,6 +16,7 @@ Submodules
|
|||
telegram.ext.commandhandler
|
||||
telegram.ext.inlinequeryhandler
|
||||
telegram.ext.messagehandler
|
||||
telegram.ext.messagequeue
|
||||
telegram.ext.filters
|
||||
telegram.ext.regexhandler
|
||||
telegram.ext.stringcommandhandler
|
||||
|
|
|
@ -32,6 +32,7 @@ Attributes:
|
|||
limit, but eventually you'll begin receiving 429 errors.
|
||||
MAX_MESSAGES_PER_SECOND (int)
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP (int)
|
||||
MAX_INLINE_QUERY_RESULTS (int)
|
||||
|
||||
The following constant have been found by experimentation:
|
||||
|
||||
|
@ -52,3 +53,4 @@ MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
|||
MAX_MESSAGES_PER_SECOND = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
||||
MAX_MESSAGE_ENTITIES = 100
|
||||
MAX_INLINE_QUERY_RESULTS = 50
|
||||
|
|
|
@ -23,7 +23,6 @@ import re
|
|||
from future.utils import string_types
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
|
@ -105,8 +104,3 @@ class CallbackQueryHandler(Handler):
|
|||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CallbackQueryHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
|
|
@ -21,7 +21,6 @@ import warnings
|
|||
|
||||
from .handler import Handler
|
||||
from telegram import Update
|
||||
from telegram.utils.deprecate import deprecate
|
||||
|
||||
|
||||
class CommandHandler(Handler):
|
||||
|
@ -110,7 +109,7 @@ class CommandHandler(Handler):
|
|||
res = self.filters(message)
|
||||
|
||||
return res and (message.text.startswith('/') and command[0] == self.command
|
||||
and command[1] == update.message.bot.username)
|
||||
and command[1].lower() == update.message.bot.username.lower())
|
||||
else:
|
||||
return False
|
||||
|
||||
|
@ -126,8 +125,3 @@ class CommandHandler(Handler):
|
|||
optional_args['args'] = message.text.split()[1:]
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CommandHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
|
|
@ -145,7 +145,9 @@ class ConversationHandler(Handler):
|
|||
raise ValueError("If 'per_chat=True', 'InlineQueryHandler' doesn't work")
|
||||
|
||||
def _get_key(self, update):
|
||||
chat, user = update.extract_chat_and_user()
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
|
||||
key = list()
|
||||
|
||||
if self.per_chat:
|
||||
|
@ -181,6 +183,7 @@ class ConversationHandler(Handler):
|
|||
res = new_state.result(timeout=self.run_async_timeout)
|
||||
except Exception as exc:
|
||||
self.logger.exception("Promise function raised exception")
|
||||
self.logger.exception("{}".format(exc))
|
||||
error = True
|
||||
|
||||
if not error and new_state.done.is_set():
|
||||
|
|
|
@ -32,7 +32,6 @@ from future.builtins import range
|
|||
|
||||
from telegram import TelegramError
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
@ -368,11 +367,3 @@ class Dispatcher(object):
|
|||
|
||||
for callback in self.error_handlers:
|
||||
callback(self.bot, update, error)
|
||||
|
||||
# old non-PEP8 Dispatcher methods
|
||||
m = "telegram.dispatcher."
|
||||
addHandler = deprecate(add_handler, m + "AddHandler", m + "add_handler")
|
||||
removeHandler = deprecate(remove_handler, m + "removeHandler", m + "remove_handler")
|
||||
addErrorHandler = deprecate(add_error_handler, m + "addErrorHandler", m + "add_error_handler")
|
||||
removeErrorHandler = deprecate(remove_error_handler, m + "removeErrorHandler",
|
||||
m + "remove_error_handler")
|
||||
|
|
|
@ -17,6 +17,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/].
|
||||
""" This module contains the Filters for use with the MessageHandler class """
|
||||
from telegram import Chat
|
||||
|
||||
|
||||
class BaseFilter(object):
|
||||
|
@ -32,9 +33,14 @@ class BaseFilter(object):
|
|||
|
||||
>>> (Filters.audio | Filters.video)
|
||||
|
||||
Not:
|
||||
|
||||
>>> ~ Filters.command
|
||||
|
||||
Also works with more than two filters:
|
||||
|
||||
>>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK)))
|
||||
>>> Filters.text & (~ Filters.forwarded)
|
||||
|
||||
If you want to create your own filters create a class inheriting from this class and implement
|
||||
a `filter` method that returns a boolean: `True` if the message should be handled, `False`
|
||||
|
@ -51,10 +57,32 @@ class BaseFilter(object):
|
|||
def __or__(self, other):
|
||||
return MergedFilter(self, or_filter=other)
|
||||
|
||||
def __invert__(self):
|
||||
return InvertedFilter(self)
|
||||
|
||||
def filter(self, message):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class InvertedFilter(BaseFilter):
|
||||
"""Represents a filter that has been inverted.
|
||||
|
||||
Args:
|
||||
f: The filter to invert
|
||||
"""
|
||||
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def filter(self, message):
|
||||
return not self.f(message)
|
||||
|
||||
def __str__(self):
|
||||
return "<telegram.ext.filters.InvertedFilter inverting {}>".format(self.f)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
class MergedFilter(BaseFilter):
|
||||
"""Represents a filter consisting of two other filters.
|
||||
|
||||
|
@ -221,3 +249,17 @@ class Filters(object):
|
|||
|
||||
def filter(self, message):
|
||||
return any([entity.type == self.entity_type for entity in message.entities])
|
||||
|
||||
class _Private(BaseFilter):
|
||||
|
||||
def filter(self, message):
|
||||
return message.chat.type == Chat.PRIVATE
|
||||
|
||||
private = _Private()
|
||||
|
||||
class _Group(BaseFilter):
|
||||
|
||||
def filter(self, message):
|
||||
return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
|
||||
|
||||
group = _Group()
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
""" This module contains the base class for handlers as used by the
|
||||
Dispatcher """
|
||||
|
||||
from telegram.utils.deprecate import deprecate
|
||||
|
||||
|
||||
class Handler(object):
|
||||
"""
|
||||
|
@ -104,19 +102,13 @@ class Handler(object):
|
|||
if self.pass_job_queue:
|
||||
optional_args['job_queue'] = dispatcher.job_queue
|
||||
if self.pass_user_data or self.pass_chat_data:
|
||||
chat, user = update.extract_chat_and_user()
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
|
||||
if self.pass_user_data:
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id]
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
|
||||
|
||||
if self.pass_chat_data:
|
||||
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
|
||||
|
||||
return optional_args
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.Handler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
collectOptionalArgs = deprecate(collect_optional_args, m + "collectOptionalArgs",
|
||||
m + "collect_optional_args")
|
||||
|
|
|
@ -21,7 +21,6 @@ import warnings
|
|||
|
||||
from .handler import Handler
|
||||
from telegram import Update
|
||||
from telegram.utils.deprecate import deprecate
|
||||
|
||||
|
||||
class MessageHandler(Handler):
|
||||
|
@ -53,7 +52,7 @@ class MessageHandler(Handler):
|
|||
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
|
||||
message_updates (Optional[bool]): Should "normal" message updates be handled? Default is
|
||||
``True``.
|
||||
channel_posts_updates (Optional[bool]): Should channel posts updates be handled? Default is
|
||||
channel_post_updates (Optional[bool]): Should channel posts updates be handled? Default is
|
||||
``True``.
|
||||
|
||||
"""
|
||||
|
@ -67,8 +66,8 @@ class MessageHandler(Handler):
|
|||
pass_user_data=False,
|
||||
pass_chat_data=False,
|
||||
message_updates=True,
|
||||
channel_posts_updates=True):
|
||||
if not message_updates and not channel_posts_updates:
|
||||
channel_post_updates=True):
|
||||
if not message_updates and not channel_post_updates:
|
||||
raise ValueError('Both message_updates & channel_post_updates are False')
|
||||
|
||||
super(MessageHandler, self).__init__(
|
||||
|
@ -80,7 +79,7 @@ class MessageHandler(Handler):
|
|||
self.filters = filters
|
||||
self.allow_edited = allow_edited
|
||||
self.message_updates = message_updates
|
||||
self.channel_posts_updates = channel_posts_updates
|
||||
self.channel_post_updates = channel_post_updates
|
||||
|
||||
# We put this up here instead of with the rest of checking code
|
||||
# in check_update since we don't wanna spam a ton
|
||||
|
@ -94,7 +93,7 @@ class MessageHandler(Handler):
|
|||
and (update.message or (update.edited_message and self.allow_edited)))
|
||||
|
||||
def _is_allowed_channel_post(self, update):
|
||||
return (self.channel_posts_updates
|
||||
return (self.channel_post_updates
|
||||
and (update.channel_post or (update.edited_channel_post and self.allow_edited)))
|
||||
|
||||
def check_update(self, update):
|
||||
|
@ -105,8 +104,7 @@ class MessageHandler(Handler):
|
|||
res = True
|
||||
|
||||
else:
|
||||
message = (update.message or update.edited_message or update.channel_post
|
||||
or update.edited_channel_post)
|
||||
message = update.effective_message
|
||||
if isinstance(self.filters, list):
|
||||
res = any(func(message) for func in self.filters)
|
||||
else:
|
||||
|
@ -121,8 +119,3 @@ class MessageHandler(Handler):
|
|||
optional_args = self.collect_optional_args(dispatcher, update)
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.MessageHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
|
330
telegram/ext/messagequeue.py
Normal file
330
telegram/ext/messagequeue.py
Normal file
|
@ -0,0 +1,330 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Module author:
|
||||
# Tymofii A. Khodniev (thodnev) <thodnev@mail.ru>
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2017
|
||||
# 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/]
|
||||
'''A throughput-limiting message processor for Telegram bots'''
|
||||
from telegram.utils import promise
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
if sys.version_info.major > 2:
|
||||
import queue as q
|
||||
else:
|
||||
import Queue as q
|
||||
|
||||
# We need to count < 1s intervals, so the most accurate timer is needed
|
||||
# Starting from Python 3.3 we have time.perf_counter which is the clock
|
||||
# with the highest resolution available to the system, so let's use it there.
|
||||
# In Python 2.7, there's no perf_counter yet, so fallback on what we have:
|
||||
# on Windows, the best available is time.clock while time.time is on
|
||||
# another platforms (M. Lutz, "Learning Python," 4ed, p.630-634)
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 3:
|
||||
curtime = time.perf_counter # pylint: disable=E1101
|
||||
else:
|
||||
curtime = time.clock if sys.platform[:3] == 'win' else time.time
|
||||
|
||||
|
||||
class DelayQueueError(RuntimeError):
|
||||
'''Indicates processing errors'''
|
||||
pass
|
||||
|
||||
|
||||
class DelayQueue(threading.Thread):
|
||||
'''Processes callbacks from queue with specified throughput limits.
|
||||
Creates a separate thread to process callbacks with delays.
|
||||
|
||||
Args:
|
||||
queue (:obj:`queue.Queue`, optional): used to pass callbacks to
|
||||
thread.
|
||||
Creates `queue.Queue` implicitly if not provided.
|
||||
burst_limit (:obj:`int`, optional): number of maximum callbacks to
|
||||
process per time-window defined by `time_limit_ms`.
|
||||
Defaults to 30.
|
||||
time_limit_ms (:obj:`int`, optional): defines width of time-window
|
||||
used when each processing limit is calculated.
|
||||
Defaults to 1000.
|
||||
exc_route (:obj:`callable`, optional): a callable, accepting 1
|
||||
positional argument; used to route exceptions from processor
|
||||
thread to main thread; is called on `Exception` subclass
|
||||
exceptions.
|
||||
If not provided, exceptions are routed through dummy handler,
|
||||
which re-raises them.
|
||||
autostart (:obj:`bool`, optional): if True, processor is started
|
||||
immediately after object's creation; if False, should be
|
||||
started manually by `start` method.
|
||||
Defaults to True.
|
||||
name (:obj:`str`, optional): thread's name.
|
||||
Defaults to ``'DelayQueue-N'``, where N is sequential
|
||||
number of object created.
|
||||
'''
|
||||
_instcnt = 0 # instance counter
|
||||
|
||||
def __init__(self,
|
||||
queue=None,
|
||||
burst_limit=30,
|
||||
time_limit_ms=1000,
|
||||
exc_route=None,
|
||||
autostart=True,
|
||||
name=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.__exit_req = False # flag to gently exit thread
|
||||
self.__class__._instcnt += 1
|
||||
if name is None:
|
||||
name = '%s-%s' % (self.__class__.__name__, self.__class__._instcnt)
|
||||
super(DelayQueue, self).__init__(name=name)
|
||||
self.daemon = False
|
||||
if autostart: # immediately start processing
|
||||
super(DelayQueue, self).start()
|
||||
|
||||
def run(self):
|
||||
'''Do not use the method except for unthreaded testing purposes,
|
||||
the method normally is automatically called by `start` method.
|
||||
'''
|
||||
times = [] # used to store each callable processing time
|
||||
while True:
|
||||
item = self._queue.get()
|
||||
if self.__exit_req:
|
||||
return # shutdown thread
|
||||
# delay routine
|
||||
now = curtime()
|
||||
t_delta = now - self.time_limit # calculate early to improve perf.
|
||||
if times and t_delta > times[-1]:
|
||||
# if last call was before the limit time-window
|
||||
# used to impr. perf. in long-interval calls case
|
||||
times = [now]
|
||||
else:
|
||||
# collect last in current limit time-window
|
||||
times = [t for t in times if t >= t_delta]
|
||||
times.append(now)
|
||||
if len(times) >= self.burst_limit: # if throughput limit was hit
|
||||
time.sleep(times[1] - t_delta)
|
||||
# finally process one
|
||||
try:
|
||||
func, args, kwargs = item
|
||||
func(*args, **kwargs)
|
||||
except Exception as exc: # re-route any exceptions
|
||||
self.exc_route(exc) # to prevent thread exit
|
||||
|
||||
def stop(self, timeout=None):
|
||||
'''Used to gently stop processor and shutdown its thread.
|
||||
|
||||
Args:
|
||||
timeout (:obj:`float`): indicates maximum time to wait for
|
||||
processor to stop and its thread to exit.
|
||||
If timeout exceeds and processor has not stopped, method
|
||||
silently returns. `is_alive` could be used afterwards
|
||||
to check the actual status. If `timeout` set to None, blocks
|
||||
until processor is shut down.
|
||||
Defaults to None.
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
self.__exit_req = True # gently request
|
||||
self._queue.put(None) # put something to unfreeze if frozen
|
||||
super(DelayQueue, self).join(timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
def _default_exception_handler(exc):
|
||||
'''Dummy exception handler which re-raises exception in thread.
|
||||
Could be possibly overwritten by subclasses.
|
||||
'''
|
||||
raise exc
|
||||
|
||||
def __call__(self, func, *args, **kwargs):
|
||||
'''Used to process callbacks in throughput-limiting thread
|
||||
through queue.
|
||||
Args:
|
||||
func (:obj:`callable`): the actual function (or any callable) that
|
||||
is processed through queue.
|
||||
*args: variable-length `func` arguments.
|
||||
**kwargs: arbitrary keyword-arguments to `func`.
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
if not self.is_alive() or self.__exit_req:
|
||||
raise DelayQueueError('Could not process callback in stopped thread')
|
||||
self._queue.put((func, args, kwargs))
|
||||
|
||||
|
||||
# The most straightforward way to implement this is to use 2 sequenital delay
|
||||
# queues, like on classic delay chain schematics in electronics.
|
||||
# So, message path is:
|
||||
# msg --> group delay if group msg, else no delay --> normal msg delay --> out
|
||||
# This way OS threading scheduler cares of timings accuracy.
|
||||
# (see time.time, time.clock, time.perf_counter, time.sleep @ docs.python.org)
|
||||
class MessageQueue(object):
|
||||
'''Implements callback processing with proper delays to avoid hitting
|
||||
Telegram's message limits.
|
||||
Contains two `DelayQueue`s, for group and for all messages, interconnected
|
||||
in delay chain. Callables are processed through *group* `DelayQueue`, then
|
||||
through *all* `DelayQueue` for group-type messages. For non-group messages,
|
||||
only the *all* `DelayQueue` is used.
|
||||
|
||||
Args:
|
||||
all_burst_limit (:obj:`int`, optional): numer of maximum *all-type*
|
||||
callbacks to process per time-window defined by
|
||||
`all_time_limit_ms`.
|
||||
Defaults to 30.
|
||||
all_time_limit_ms (:obj:`int`, optional): defines width of *all-type*
|
||||
time-window used when each processing limit is calculated.
|
||||
Defaults to 1000 ms.
|
||||
group_burst_limit (:obj:`int`, optional): numer of maximum *group-type*
|
||||
callbacks to process per time-window defined by
|
||||
`group_time_limit_ms`.
|
||||
Defaults to 20.
|
||||
group_time_limit_ms (:obj:`int`, optional): defines width of
|
||||
*group-type* time-window used when each processing limit is
|
||||
calculated.
|
||||
Defaults to 60000 ms.
|
||||
exc_route (:obj:`callable`, optional): a callable, accepting one
|
||||
positional argument; used to route exceptions from processor
|
||||
threads to main thread; is called on `Exception` subclass
|
||||
exceptions.
|
||||
If not provided, exceptions are routed through dummy handler,
|
||||
which re-raises them.
|
||||
autostart (:obj:`bool`, optional): if True, processors are started
|
||||
immediately after object's creation; if False, should be
|
||||
started manually by `start` method.
|
||||
Defaults to True.
|
||||
|
||||
Attributes:
|
||||
_all_delayq (:obj:`telegram.ext.messagequeue.DelayQueue`): actual
|
||||
`DelayQueue` used for *all-type* callback processing
|
||||
_group_delayq (:obj:`telegram.ext.messagequeue.DelayQueue`): actual
|
||||
`DelayQueue` used for *group-type* callback processing
|
||||
'''
|
||||
|
||||
def __init__(self,
|
||||
all_burst_limit=30,
|
||||
all_time_limit_ms=1000,
|
||||
group_burst_limit=20,
|
||||
group_time_limit_ms=60000,
|
||||
exc_route=None,
|
||||
autostart=True):
|
||||
# create accoring 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)
|
||||
self._group_delayq = DelayQueue(
|
||||
burst_limit=group_burst_limit,
|
||||
time_limit_ms=group_time_limit_ms,
|
||||
exc_route=exc_route,
|
||||
autostart=autostart)
|
||||
|
||||
def start(self):
|
||||
'''Method is used to manually start the `MessageQueue` processing
|
||||
|
||||
Returns:
|
||||
None
|
||||
'''
|
||||
self._all_delayq.start()
|
||||
self._group_delayq.start()
|
||||
|
||||
def stop(self, timeout=None):
|
||||
self._group_delayq.stop(timeout=timeout)
|
||||
self._all_delayq.stop(timeout=timeout)
|
||||
|
||||
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docsting if any
|
||||
|
||||
def __call__(self, promise, is_group_msg=False):
|
||||
'''Processes callables in troughput-limiting queues to avoid
|
||||
hitting limits (specified with \*_burst_limit and *\_time_limit_ms).
|
||||
Args:
|
||||
promise (:obj:`callable`): mainly the
|
||||
:obj:`telegram.utils.promise.Promise` (see Notes for other
|
||||
callables), that is processed in delay queues
|
||||
is_group_msg (:obj:`bool`, optional): defines whether `promise`
|
||||
would be processed in *group*+*all* `DelayQueue`s
|
||||
(if set to ``True``), or only through *all* `DelayQueue`
|
||||
(if set to ``False``), resulting in needed delays to avoid
|
||||
hitting specified limits.
|
||||
Defaults to ``True``.
|
||||
|
||||
Notes:
|
||||
Method is designed to accept :obj:`telegram.utils.promise.Promise`
|
||||
as `promise` argument, but other callables could be used too.
|
||||
For example, lambdas or simple functions could be used to wrap
|
||||
original func to be called with needed args.
|
||||
In that case, be sure that either wrapper func does not raise
|
||||
outside exceptions or the proper `exc_route` handler is provided.
|
||||
|
||||
Returns:
|
||||
:obj:`callable` used as `promise` argument.
|
||||
'''
|
||||
if not is_group_msg: # ignore middle group delay
|
||||
self._all_delayq(promise)
|
||||
else: # use middle group delay
|
||||
self._group_delayq(self._all_delayq, promise)
|
||||
return promise
|
||||
|
||||
|
||||
def queuedmessage(method):
|
||||
'''A decorator to be used with `telegram.bot.Bot` send* methods.
|
||||
|
||||
Note:
|
||||
As it probably wouldn't be a good idea to make this decorator a
|
||||
property, it had been coded as decorator function, so it implies that
|
||||
**first positional argument to wrapped MUST be self**\.
|
||||
|
||||
The next object attributes are used by decorator:
|
||||
|
||||
Attributes:
|
||||
self._is_messages_queued_default (:obj:`bool`): Value to provide
|
||||
class-defaults to `queued` kwarg if not provided during wrapped
|
||||
method call.
|
||||
self._msg_queue (:obj:`telegram.ext.messagequeue.MessageQueue`):
|
||||
The actual `MessageQueue` used to delay outbond messages according
|
||||
to specified time-limits.
|
||||
|
||||
Wrapped method starts accepting the next kwargs:
|
||||
|
||||
Args:
|
||||
queued (:obj:`bool`, optional): if set to ``True``, the `MessageQueue`
|
||||
is used to process output messages.
|
||||
Defaults to `self._is_queued_out`.
|
||||
isgroup (:obj:`bool`, optional): if set to ``True``, the message is
|
||||
meant to be group-type (as there's no obvious way to determine its
|
||||
type in other way at the moment). Group-type messages could have
|
||||
additional processing delay according to limits set in
|
||||
`self._out_queue`.
|
||||
Defaults to ``False``.
|
||||
|
||||
Returns:
|
||||
Either :obj:`telegram.utils.promise.Promise` in case call is queued,
|
||||
or original method's return value if it's not.
|
||||
'''
|
||||
|
||||
@functools.wraps(method)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
queued = kwargs.pop('queued', self._is_messages_queued_default)
|
||||
isgroup = kwargs.pop('isgroup', False)
|
||||
if queued:
|
||||
prom = promise.Promise(method, args, kwargs)
|
||||
return self._msg_queue(prom, isgroup)
|
||||
return method(self, *args, **kwargs)
|
||||
|
||||
return wrapped
|
|
@ -71,7 +71,10 @@ class RegexHandler(Handler):
|
|||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
pass_chat_data=False,
|
||||
allow_edited=False,
|
||||
message_updates=True,
|
||||
channel_post_updates=False):
|
||||
super(RegexHandler, self).__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
|
@ -85,17 +88,30 @@ class RegexHandler(Handler):
|
|||
self.pattern = pattern
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
self.allow_edited = allow_edited
|
||||
self.message_updates = message_updates
|
||||
self.channel_post_updates = channel_post_updates
|
||||
|
||||
def _is_allowed_message(self, update):
|
||||
return (self.message_updates
|
||||
and (update.message or (update.edited_message and self.allow_edited)))
|
||||
|
||||
def _is_allowed_channel_post(self, update):
|
||||
return (self.channel_post_updates
|
||||
and (update.channel_post or (update.edited_channel_post and self.allow_edited)))
|
||||
|
||||
def check_update(self, update):
|
||||
if isinstance(update, Update) and update.message and update.message.text:
|
||||
match = re.match(self.pattern, update.message.text)
|
||||
if (isinstance(update, Update)
|
||||
and (self._is_allowed_message(update) or self._is_allowed_channel_post(update))
|
||||
and update.effective_message.text):
|
||||
match = re.match(self.pattern, update.effective_message.text)
|
||||
return bool(match)
|
||||
else:
|
||||
return False
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher, update)
|
||||
match = re.match(self.pattern, update.message.text)
|
||||
match = re.match(self.pattern, update.effective_message.text)
|
||||
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = match.groups()
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
""" This module contains the StringCommandHandler class """
|
||||
|
||||
from .handler import Handler
|
||||
from telegram.utils.deprecate import deprecate
|
||||
|
||||
|
||||
class StringCommandHandler(Handler):
|
||||
|
@ -69,8 +68,3 @@ class StringCommandHandler(Handler):
|
|||
optional_args['args'] = update.split()[1:]
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.StringCommandHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
|
|
@ -23,7 +23,6 @@ import re
|
|||
from future.utils import string_types
|
||||
|
||||
from .handler import Handler
|
||||
from telegram.utils.deprecate import deprecate
|
||||
|
||||
|
||||
class StringRegexHandler(Handler):
|
||||
|
@ -84,8 +83,3 @@ class StringRegexHandler(Handler):
|
|||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.StringRegexHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
|
|
@ -62,6 +62,9 @@ class Updater(object):
|
|||
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
|
||||
the user's responsibility to create it using a `Request` instance with a large enough
|
||||
connection pool.
|
||||
user_sig_handler (Optional[function]): Takes ``signum, frame`` as positional arguments.
|
||||
This will be called when a signal is received, defaults are (SIGINT, SIGTERM, SIGABRT)
|
||||
setable with Updater.idle(stop_signals=(signals))
|
||||
request_kwargs (Optional[dict]): Keyword args to control the creation of a request object
|
||||
(ignored if `bot` argument is used).
|
||||
|
||||
|
@ -71,7 +74,14 @@ class Updater(object):
|
|||
"""
|
||||
_request = None
|
||||
|
||||
def __init__(self, token=None, base_url=None, workers=4, bot=None, request_kwargs=None):
|
||||
def __init__(self,
|
||||
token=None,
|
||||
base_url=None,
|
||||
workers=4,
|
||||
bot=None,
|
||||
user_sig_handler=None,
|
||||
request_kwargs=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):
|
||||
|
@ -92,6 +102,7 @@ class Updater(object):
|
|||
request_kwargs['con_pool_size'] = workers + 4
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = Bot(token, base_url, request=self._request)
|
||||
self.user_sig_handler = user_sig_handler
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue(self.bot)
|
||||
self.__exception_event = Event()
|
||||
|
@ -192,7 +203,8 @@ class Updater(object):
|
|||
key=None,
|
||||
clean=False,
|
||||
bootstrap_retries=0,
|
||||
webhook_url=None):
|
||||
webhook_url=None,
|
||||
allowed_updates=None):
|
||||
"""
|
||||
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
|
||||
|
@ -215,9 +227,10 @@ class Updater(object):
|
|||
| < 0 - retry indefinitely
|
||||
| 0 - no retries (default)
|
||||
| > 0 - retry up to X times
|
||||
webhook_url (Optional[str]): Explicitly specifiy the webhook url.
|
||||
webhook_url (Optional[str]): Explicitly specify the webhook url.
|
||||
Useful behind NAT, reverse proxy, etc. Default is derived from
|
||||
`listen`, `port` & `url_path`.
|
||||
allowed_updates (Optional[list[str]]): Passed to Bot.setWebhook
|
||||
|
||||
Returns:
|
||||
Queue: The update queue that can be filled from the main thread
|
||||
|
@ -231,7 +244,7 @@ class Updater(object):
|
|||
self.job_queue.start()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher"),
|
||||
self._init_thread(self._start_webhook, "updater", listen, port, url_path, cert,
|
||||
key, bootstrap_retries, clean, webhook_url)
|
||||
key, bootstrap_retries, clean, webhook_url, allowed_updates)
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
@ -247,7 +260,7 @@ class Updater(object):
|
|||
cur_interval = poll_interval
|
||||
self.logger.debug('Updater thread started')
|
||||
|
||||
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='')
|
||||
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='', allowed_updates=None)
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
|
@ -295,7 +308,7 @@ class Updater(object):
|
|||
return current_interval
|
||||
|
||||
def _start_webhook(self, listen, port, url_path, cert, key, bootstrap_retries, clean,
|
||||
webhook_url):
|
||||
webhook_url, allowed_updates):
|
||||
self.logger.debug('Updater thread started')
|
||||
use_ssl = cert is not None and key is not None
|
||||
if not url_path.startswith('/'):
|
||||
|
@ -316,7 +329,8 @@ class Updater(object):
|
|||
max_retries=bootstrap_retries,
|
||||
clean=clean,
|
||||
webhook_url=webhook_url,
|
||||
cert=open(cert, 'rb'))
|
||||
cert=open(cert, 'rb'),
|
||||
allowed_updates=allowed_updates)
|
||||
elif clean:
|
||||
self.logger.warning("cleaning updates is not supported if "
|
||||
"SSL-termination happens elsewhere; skipping")
|
||||
|
@ -346,7 +360,7 @@ class Updater(object):
|
|||
def _gen_webhook_url(listen, port, url_path):
|
||||
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
|
||||
|
||||
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
|
||||
def _bootstrap(self, max_retries, clean, webhook_url, allowed_updates, cert=None):
|
||||
retries = 0
|
||||
while 1:
|
||||
|
||||
|
@ -357,7 +371,8 @@ class Updater(object):
|
|||
self._clean_updates()
|
||||
sleep(1)
|
||||
|
||||
self.bot.setWebhook(url=webhook_url, certificate=cert)
|
||||
self.bot.setWebhook(
|
||||
url=webhook_url, certificate=cert, allowed_updates=allowed_updates)
|
||||
except (Unauthorized, InvalidToken):
|
||||
raise
|
||||
except TelegramError:
|
||||
|
@ -422,6 +437,8 @@ class Updater(object):
|
|||
self.is_idle = False
|
||||
if self.running:
|
||||
self.stop()
|
||||
if self.user_sig_handler:
|
||||
self.user_sig_handler(signum, frame)
|
||||
else:
|
||||
self.logger.warning('Exiting immediately!')
|
||||
import os
|
||||
|
|
|
@ -202,7 +202,7 @@ class Message(TelegramObject):
|
|||
data = super(Message, Message).de_json(data, bot)
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['date'] = datetime.fromtimestamp(data['date'])
|
||||
data['date'] = Message._fromtimestamp(data['date'])
|
||||
data['chat'] = Chat.de_json(data.get('chat'), bot)
|
||||
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
|
||||
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
|
||||
|
|
|
@ -73,6 +73,10 @@ class Update(TelegramObject):
|
|||
self.channel_post = channel_post
|
||||
self.edited_channel_post = edited_channel_post
|
||||
|
||||
self._effective_user = None
|
||||
self._effective_chat = None
|
||||
self._effective_message = None
|
||||
|
||||
@staticmethod
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
|
@ -99,25 +103,23 @@ class Update(TelegramObject):
|
|||
|
||||
return Update(**data)
|
||||
|
||||
def extract_chat_and_user(self):
|
||||
@property
|
||||
def effective_user(self):
|
||||
"""
|
||||
A property that contains the ``User`` that sent this update, no matter what kind of update
|
||||
this is. Will be ``None`` for channel posts.
|
||||
"""
|
||||
Helper method to get the sender's chat and user objects from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
tuple: of (chat, user), with None-values if no object could not be found.
|
||||
"""
|
||||
if self._effective_user:
|
||||
return self._effective_user
|
||||
|
||||
user = None
|
||||
chat = None
|
||||
|
||||
if self.message:
|
||||
user = self.message.from_user
|
||||
chat = self.message.chat
|
||||
|
||||
elif self.edited_message:
|
||||
user = self.edited_message.from_user
|
||||
chat = self.edited_message.chat
|
||||
|
||||
elif self.inline_query:
|
||||
user = self.inline_query.from_user
|
||||
|
@ -127,51 +129,67 @@ class Update(TelegramObject):
|
|||
|
||||
elif self.callback_query:
|
||||
user = self.callback_query.from_user
|
||||
chat = self.callback_query.message.chat if self.callback_query.message else None
|
||||
|
||||
return chat, user
|
||||
self._effective_user = user
|
||||
return user
|
||||
|
||||
def extract_message_text(self):
|
||||
@property
|
||||
def effective_chat(self):
|
||||
"""
|
||||
Helper method to get the message text from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
str: The extracted message text
|
||||
|
||||
Raises:
|
||||
ValueError: If no message text was found in the update
|
||||
|
||||
A property that contains the ``Chat`` that this update was sent in, no matter what kind of
|
||||
update this is. Will be ``None`` for inline queries and chosen inline results.
|
||||
"""
|
||||
|
||||
if self._effective_chat:
|
||||
return self._effective_chat
|
||||
|
||||
chat = None
|
||||
|
||||
if self.message:
|
||||
return self.message.text
|
||||
chat = self.message.chat
|
||||
|
||||
elif self.edited_message:
|
||||
return self.edited_message.text
|
||||
elif self.callback_query:
|
||||
return self.callback_query.message.text
|
||||
else:
|
||||
raise ValueError("Update contains no message text.")
|
||||
chat = self.edited_message.chat
|
||||
|
||||
def extract_entities(self):
|
||||
elif self.callback_query and self.callback_query.message:
|
||||
chat = self.callback_query.message.chat
|
||||
|
||||
elif self.channel_post:
|
||||
chat = self.channel_post.chat
|
||||
|
||||
elif self.edited_channel_post:
|
||||
chat = self.edited_channel_post.chat
|
||||
|
||||
self._effective_chat = chat
|
||||
return chat
|
||||
|
||||
@property
|
||||
def effective_message(self):
|
||||
"""
|
||||
Helper method to get parsed entities from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
|
||||
text that belongs to them, calculated based on UTF-16 codepoints.
|
||||
|
||||
Raises:
|
||||
ValueError: If no entities were found in the update
|
||||
|
||||
A property that contains the ``Message`` included in this update, no matter what kind
|
||||
of update this is. Will be ``None`` for inline queries, chosen inline results and callback
|
||||
queries from inline messages.
|
||||
"""
|
||||
|
||||
if self._effective_message:
|
||||
return self._effective_message
|
||||
|
||||
message = None
|
||||
|
||||
if self.message:
|
||||
return self.message.parse_entities()
|
||||
message = self.message
|
||||
|
||||
elif self.edited_message:
|
||||
return self.edited_message.parse_entities()
|
||||
message = self.edited_message
|
||||
|
||||
elif self.callback_query:
|
||||
return self.callback_query.message.parse_entities()
|
||||
else:
|
||||
raise ValueError("No message object found in self, therefore no entities available.")
|
||||
message = self.callback_query.message
|
||||
|
||||
elif self.channel_post:
|
||||
message = self.channel_post
|
||||
|
||||
elif self.edited_channel_post:
|
||||
message = self.edited_channel_post
|
||||
|
||||
self._effective_message = message
|
||||
return message
|
||||
|
|
|
@ -28,6 +28,7 @@ except ImportError:
|
|||
|
||||
import certifi
|
||||
import urllib3
|
||||
import urllib3.contrib.appengine
|
||||
from urllib3.connection import HTTPConnection
|
||||
from urllib3.util.timeout import Timeout
|
||||
try:
|
||||
|
@ -93,7 +94,11 @@ class Request(object):
|
|||
proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy')
|
||||
|
||||
if not proxy_url:
|
||||
mgr = urllib3.PoolManager(**kwargs)
|
||||
if urllib3.contrib.appengine.is_appengine_sandbox():
|
||||
# Use URLFetch service if running in App Engine
|
||||
mgr = urllib3.contrib.appengine.AppEngineManager()
|
||||
else:
|
||||
mgr = urllib3.PoolManager(**kwargs)
|
||||
else:
|
||||
kwargs.update(urllib3_proxy_kwargs)
|
||||
if proxy_url.startswith('socks'):
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
__version__ = '5.3.0'
|
||||
__version__ = '5.3.1'
|
||||
|
|
|
@ -178,6 +178,18 @@ class FiltersTest(BaseTest, unittest.TestCase):
|
|||
self.message.entities = [self.e(MessageEntity.BOLD), self.e(MessageEntity.MENTION)]
|
||||
self.assertTrue(Filters.entity(MessageEntity.MENTION)(self.message))
|
||||
|
||||
def test_private_filter(self):
|
||||
self.assertTrue(Filters.private(self.message))
|
||||
self.message.chat.type = "group"
|
||||
self.assertFalse(Filters.private(self.message))
|
||||
|
||||
def test_group_fileter(self):
|
||||
self.assertFalse(Filters.group(self.message))
|
||||
self.message.chat.type = "group"
|
||||
self.assertTrue(Filters.group(self.message))
|
||||
self.message.chat.type = "supergroup"
|
||||
self.assertTrue(Filters.group(self.message))
|
||||
|
||||
def test_and_filters(self):
|
||||
self.message.text = 'test'
|
||||
self.message.forward_date = True
|
||||
|
@ -226,6 +238,32 @@ class FiltersTest(BaseTest, unittest.TestCase):
|
|||
r"<telegram.ext.filters.(Filters.)?_Forwarded object at .*?> or "
|
||||
r"<telegram.ext.filters.(Filters.)?entity object at .*?>>>")
|
||||
|
||||
def test_inverted_filters(self):
|
||||
self.message.text = '/test'
|
||||
self.assertTrue((Filters.command)(self.message))
|
||||
self.assertFalse((~Filters.command)(self.message))
|
||||
self.message.text = 'test'
|
||||
self.assertFalse((Filters.command)(self.message))
|
||||
self.assertTrue((~Filters.command)(self.message))
|
||||
|
||||
def test_inverted_and_filters(self):
|
||||
self.message.text = '/test'
|
||||
self.message.forward_date = 1
|
||||
self.assertTrue((Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertFalse((~Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertFalse((Filters.forwarded & ~Filters.command)(self.message))
|
||||
self.assertFalse((~(Filters.forwarded & Filters.command))(self.message))
|
||||
self.message.forward_date = None
|
||||
self.assertFalse((Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertTrue((~Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertFalse((Filters.forwarded & ~Filters.command)(self.message))
|
||||
self.assertTrue((~(Filters.forwarded & Filters.command))(self.message))
|
||||
self.message.text = 'test'
|
||||
self.assertFalse((Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertFalse((~Filters.forwarded & Filters.command)(self.message))
|
||||
self.assertFalse((Filters.forwarded & ~Filters.command)(self.message))
|
||||
self.assertTrue((~(Filters.forwarded & Filters.command))(self.message))
|
||||
|
||||
def test_faulty_custom_filter(self):
|
||||
|
||||
class _CustomFilter(BaseFilter):
|
||||
|
|
91
tests/test_messagequeue.py
Normal file
91
tests/test_messagequeue.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
'''This module contains telegram.ext.messagequeue test logic'''
|
||||
from __future__ import print_function, division
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__) + os.sep + '..')
|
||||
from tests.base import BaseTest
|
||||
|
||||
from telegram.ext import messagequeue as mq
|
||||
|
||||
|
||||
class DelayQueueTest(BaseTest, unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._N = kwargs.pop('N', 128)
|
||||
self._msglimit = kwargs.pop('burst_limit', 30)
|
||||
self._timelimit = kwargs.pop('time_limit_ms', 1000)
|
||||
self._margin = kwargs.pop('margin_ms', 0)
|
||||
isprint = kwargs.pop('isprint', False)
|
||||
|
||||
def noprint(*args, **kwargs):
|
||||
pass
|
||||
|
||||
self._print = print if isprint else noprint
|
||||
super(DelayQueueTest, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
print = self._print
|
||||
print('Self-test with N = {} msgs, burst_limit = {} msgs, '
|
||||
'time_limit = {:.2f} ms, margin = {:.2f} ms'
|
||||
''.format(self._N, self._msglimit, self._timelimit, self._margin))
|
||||
self.testtimes = []
|
||||
|
||||
def testcall():
|
||||
self.testtimes.append(mq.curtime())
|
||||
|
||||
self.testcall = testcall
|
||||
|
||||
def test_delayqueue_limits(self):
|
||||
'''Test that DelayQueue dispatched calls don't hit time-window limit'''
|
||||
print = self._print
|
||||
dsp = mq.DelayQueue(
|
||||
burst_limit=self._msglimit, time_limit_ms=self._timelimit, autostart=True)
|
||||
print('Started dispatcher {}\nStatus: {}'
|
||||
''.format(dsp, ['inactive', 'active'][dsp.is_alive()]))
|
||||
self.assertTrue(dsp.is_alive())
|
||||
|
||||
print('Dispatching {} calls @ {}'.format(self._N, time.asctime()))
|
||||
|
||||
for i in range(self._N):
|
||||
dsp(self.testcall)
|
||||
|
||||
print('Queue filled, waiting 4 dispatch finish @ ' + str(time.asctime()))
|
||||
|
||||
starttime = mq.curtime()
|
||||
app_endtime = (
|
||||
(self._N * self._msglimit /
|
||||
(1000 * self._timelimit)) + starttime + 20) # wait up to 20 sec more than needed
|
||||
while not dsp._queue.empty() and mq.curtime() < app_endtime:
|
||||
time.sleep(1)
|
||||
self.assertTrue(dsp._queue.empty()) # check loop exit condition
|
||||
|
||||
dsp.stop()
|
||||
print('Dispatcher ' + ['stopped', '!NOT STOPPED!'][dsp.is_alive()] + ' @ ' + str(
|
||||
time.asctime()))
|
||||
self.assertFalse(dsp.is_alive())
|
||||
|
||||
self.assertTrue(self.testtimes or self._N == 0)
|
||||
print('Calculating call time windows')
|
||||
passes, fails = [], []
|
||||
delta = (self._timelimit - self._margin) / 1000
|
||||
it = enumerate(range(self._msglimit + 1, len(self.testtimes)))
|
||||
for start, stop in it:
|
||||
part = self.testtimes[start:stop]
|
||||
if (part[-1] - part[0]) >= delta:
|
||||
passes.append(part)
|
||||
else:
|
||||
fails.append(part)
|
||||
|
||||
print('Tested: {}, Passed: {}, Failed: {}'
|
||||
''.format(len(passes + fails), len(passes), len(fails)))
|
||||
if fails:
|
||||
print('(!) Got mismatches: ' + ';\n'.join(map(str, fails)))
|
||||
self.assertFalse(fails)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -76,16 +76,20 @@ class UpdateTest(BaseTest, unittest.TestCase):
|
|||
self.assertEqual(update['update_id'], self.update_id)
|
||||
self.assertTrue(isinstance(update['message'], telegram.Message))
|
||||
|
||||
def test_extract_chat_and_user(self):
|
||||
def test_effective_chat(self):
|
||||
update = telegram.Update.de_json(self.json_dict, self._bot)
|
||||
chat, user = update.extract_chat_and_user()
|
||||
chat = update.effective_chat
|
||||
self.assertEqual(update.message.chat, chat)
|
||||
|
||||
def test_effective_user(self):
|
||||
update = telegram.Update.de_json(self.json_dict, self._bot)
|
||||
user = update.effective_user
|
||||
self.assertEqual(update.message.from_user, user)
|
||||
|
||||
def test_extract_message_text(self):
|
||||
def test_effective_message(self):
|
||||
update = telegram.Update.de_json(self.json_dict, self._bot)
|
||||
text = update.extract_message_text()
|
||||
self.assertEqual(update.message.text, text)
|
||||
message = update.effective_message
|
||||
self.assertEqual(update.message.text, message.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -189,15 +189,15 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
d = self.updater.dispatcher
|
||||
from telegram.ext import Filters
|
||||
handler = MessageHandler(Filters.text, self.telegramHandlerEditedTest, allow_edited=True)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, 'Test')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
handler = MessageHandler(Filters.text, self.telegramHandlerEditedTest, allow_edited=False)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.reset()
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
|
@ -250,6 +250,10 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test@MockBot')
|
||||
message.text = "/test@mockbot"
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test@mockbot')
|
||||
|
||||
# directed at other bot
|
||||
self.reset()
|
||||
|
@ -259,9 +263,9 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
self.assertTrue(None is self.received_message)
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=False)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.reset()
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
|
@ -716,7 +720,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
ip = '127.0.0.1'
|
||||
port = randrange(1024, 49152) # select random port for travis
|
||||
thr = Thread(
|
||||
target=self.updater._start_webhook, args=(ip, port, '', None, None, 0, False, None))
|
||||
target=self.updater._start_webhook,
|
||||
args=(ip, port, '', None, None, 0, False, None, None))
|
||||
thr.start()
|
||||
|
||||
sleep(0.5)
|
||||
|
@ -789,6 +794,23 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
sleep(1)
|
||||
self.assertFalse(self.updater.running)
|
||||
|
||||
def test_userSignal(self):
|
||||
self._setup_updater('Test7', messages=0)
|
||||
|
||||
tempVar = {'a': 0}
|
||||
|
||||
def userSignalInc(signum, frame):
|
||||
tempVar['a'] = 1
|
||||
|
||||
self.updater.user_sig_handler = userSignalInc
|
||||
self.updater.start_polling(poll_interval=0.01)
|
||||
Thread(target=self.signalsender).start()
|
||||
self.updater.idle()
|
||||
# If we get this far, idle() ran through
|
||||
sleep(1)
|
||||
self.assertFalse(self.updater.running)
|
||||
self.assertTrue(tempVar['a'] != 0)
|
||||
|
||||
def test_createBot(self):
|
||||
self.updater = Updater('123:abcd')
|
||||
self.assertIsNotNone(self.updater.bot)
|
||||
|
@ -832,7 +854,7 @@ class MockBot(object):
|
|||
|
||||
return update
|
||||
|
||||
def setWebhook(self, url=None, certificate=None):
|
||||
def setWebhook(self, url=None, certificate=None, allowed_updates=None):
|
||||
if self.bootstrap_retries is None:
|
||||
return
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue