diff --git a/README.rst b/README.rst index 7c0bb95e2..133db3f84 100644 --- a/README.rst +++ b/README.rst @@ -371,9 +371,8 @@ Our bot is now up and running (go ahead and try it)! It's not doing anything yet >>> def echo(bot, update): ... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text) ... - >>> from telegram.ext import MessageHandler - >>> from telegram.ext import filters - >>> echo_handler = MessageHandler([filters.TEXT], echo) + >>> from telegram.ext import MessageHandler, Filters + >>> echo_handler = MessageHandler([Filters.text], echo) >>> dispatcher.addHandler(echo_handler) Our bot should now reply to all text messages that are not a command with a message that has the same content. diff --git a/docs/source/telegram.ext.filters.rst b/docs/source/telegram.ext.filters.rst deleted file mode 100644 index a04a2d1c1..000000000 --- a/docs/source/telegram.ext.filters.rst +++ /dev/null @@ -1,7 +0,0 @@ -telegram.ext.filters module -=========================== - -.. automodule:: telegram.ext.filters - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/telegram.ext.rst b/docs/source/telegram.ext.rst index a924e021e..04cb17f6f 100644 --- a/docs/source/telegram.ext.rst +++ b/docs/source/telegram.ext.rst @@ -14,7 +14,6 @@ Submodules telegram.ext.commandhandler telegram.ext.inlinequeryhandler telegram.ext.messagehandler - telegram.ext.filters telegram.ext.regexhandler telegram.ext.stringcommandhandler telegram.ext.stringregexhandler diff --git a/examples/clibot.py b/examples/clibot.py index 752f10d68..445c8d783 100644 --- a/examples/clibot.py +++ b/examples/clibot.py @@ -19,7 +19,7 @@ Type 'stop' on the command line to stop the bot. """ from telegram.ext import Updater, StringCommandHandler, StringRegexHandler, \ - MessageHandler, CommandHandler, RegexHandler, filters + MessageHandler, CommandHandler, RegexHandler, Filters from telegram.ext.dispatcher import run_async from time import sleep import logging @@ -115,7 +115,7 @@ def main(): dp.addHandler(CommandHandler("start", start)) dp.addHandler(CommandHandler("help", help)) # Message handlers only receive updates that don't contain commands - dp.addHandler(MessageHandler([filters.TEXT], message)) + dp.addHandler(MessageHandler([Filters.text], message)) # Regex handlers will receive all updates on which their regex matches, # but we have to add it in a separate group, since in one group, # only one handler will be executed diff --git a/examples/echobot2.py b/examples/echobot2.py index 492fc0e38..4833b9bb1 100644 --- a/examples/echobot2.py +++ b/examples/echobot2.py @@ -17,7 +17,7 @@ Press Ctrl-C on the command line or send a signal to the process to stop the bot. """ -from telegram.ext import Updater, CommandHandler, MessageHandler, filters +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters import logging # Enable logging @@ -58,7 +58,7 @@ def main(): dp.addHandler(CommandHandler("help", help)) # on noncommand i.e message - echo the message on Telegram - dp.addHandler(MessageHandler([filters.TEXT], echo)) + dp.addHandler(MessageHandler([Filters.text], echo)) # log all errors dp.addErrorHandler(error) diff --git a/examples/inlinekeyboard_example.py b/examples/inlinekeyboard_example.py index 42ce67825..d17eca900 100644 --- a/examples/inlinekeyboard_example.py +++ b/examples/inlinekeyboard_example.py @@ -9,7 +9,7 @@ import logging from telegram import Emoji, ForceReply, InlineKeyboardButton, \ InlineKeyboardMarkup from telegram.ext import Updater, CommandHandler, MessageHandler, \ - CallbackQueryHandler, filters + CallbackQueryHandler, Filters logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - ' '%(message)s', @@ -105,7 +105,7 @@ updater = Updater("TOKEN") # The command updater.dispatcher.addHandler(CommandHandler('set', set_value)) # The answer -updater.dispatcher.addHandler(MessageHandler([filters.TEXT], entered_value)) +updater.dispatcher.addHandler(MessageHandler([Filters.text], entered_value)) # The confirmation updater.dispatcher.addHandler(CallbackQueryHandler(confirm_value)) updater.dispatcher.addHandler(CommandHandler('start', help)) diff --git a/examples/state_machine_bot.py b/examples/state_machine_bot.py index 0ffe6328c..2017a7f18 100644 --- a/examples/state_machine_bot.py +++ b/examples/state_machine_bot.py @@ -6,7 +6,7 @@ import logging from telegram import Emoji, ForceReply, ReplyKeyboardMarkup, KeyboardButton -from telegram.ext import Updater, CommandHandler, MessageHandler, filters +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - ' '%(message)s', @@ -93,7 +93,7 @@ updater = Updater("TOKEN") # The command updater.dispatcher.addHandler(CommandHandler('set', set_value)) # The answer and confirmation -updater.dispatcher.addHandler(MessageHandler([filters.TEXT], set_value)) +updater.dispatcher.addHandler(MessageHandler([Filters.text], set_value)) updater.dispatcher.addHandler(CommandHandler('cancel', cancel)) updater.dispatcher.addHandler(CommandHandler('start', help)) updater.dispatcher.addHandler(CommandHandler('help', help)) diff --git a/telegram/base.py b/telegram/base.py index f86f1433a..24033c306 100644 --- a/telegram/base.py +++ b/telegram/base.py @@ -64,8 +64,9 @@ class TelegramObject(object): """ data = dict() - for key, value in self.__dict__.items(): - if value or value == '': + for key in iter(self.__dict__): + value = self.__dict__[key] + if value is not None: if hasattr(value, 'to_dict'): data[key] = value.to_dict() else: diff --git a/telegram/bot.py b/telegram/bot.py index 172847482..845fe885c 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -919,7 +919,7 @@ class Bot(TelegramObject): data['cache_time'] = cache_time if is_personal: data['is_personal'] = is_personal - if next_offset or next_offset == '': + if next_offset is not None: data['next_offset'] = next_offset if switch_pm_text: data['switch_pm_text'] = switch_pm_text @@ -1443,7 +1443,7 @@ class Bot(TelegramObject): data = {} - if webhook_url or webhook_url == '': + if webhook_url is not None: data['url'] = webhook_url if certificate: data['certificate'] = certificate diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index f2946ff99..3c6eb304f 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -27,7 +27,7 @@ from .choseninlineresulthandler import ChosenInlineResultHandler from .commandhandler import CommandHandler from .handler import Handler from .inlinequeryhandler import InlineQueryHandler -from .messagehandler import MessageHandler +from .messagehandler import MessageHandler, Filters from .regexhandler import RegexHandler from .stringcommandhandler import StringCommandHandler from .stringregexhandler import StringRegexHandler @@ -35,5 +35,5 @@ from .typehandler import TypeHandler __all__ = ('Dispatcher', 'JobQueue', 'Updater', 'CallbackQueryHandler', 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', - 'InlineQueryHandler', 'MessageHandler', 'RegexHandler', + 'InlineQueryHandler', 'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler') diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index 86dd2152d..351fe278d 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -24,11 +24,7 @@ from functools import wraps from threading import Thread, BoundedSemaphore, Lock, Event, current_thread from time import sleep -# Adjust for differences in Python versions -try: - from queue import Empty # flake8: noqa -except ImportError: - from Queue import Empty # flake8: noqa +from queue import Empty from telegram import (TelegramError, NullHandler) from telegram.ext.handler import Handler @@ -100,6 +96,9 @@ class Dispatcher(object): self.update_queue = update_queue self.handlers = {} + """:type: dict[int, list[Handler]""" + self.groups = [] + """:type: list[int]""" self.error_handlers = [] self.logger = logging.getLogger(__name__) @@ -174,8 +173,8 @@ class Dispatcher(object): self.dispatchError(None, update) else: - for group in self.handlers.values(): - for handler in group: + for group in self.groups: + for handler in self.handlers[group]: try: if handler.checkUpdate(update): handler.handleUpdate(update, self) @@ -188,7 +187,7 @@ class Dispatcher(object): try: self.dispatchError(update, te) - except: + except Exception: self.logger.exception( 'An uncaught error was raised while ' 'handling the error') @@ -196,7 +195,7 @@ class Dispatcher(object): break # Errors should not stop the thread - except: + except Exception: self.logger.exception( 'An uncaught error was raised while ' 'processing the update') @@ -204,25 +203,39 @@ class Dispatcher(object): def addHandler(self, handler, group=DEFAULT_GROUP): """ - Register a handler. A handler must be an instance of a subclass of - telegram.ext.Handler. All handlers are organized in groups, the default - group is int(0), but any object can identify a group. Every update will - be tested against each handler in each group from first-added to last- - added. If the update has been handled in one group, it will not be - tested against other handlers in that group. That means an update can - only be handled 0 or 1 times per group, but multiple times across all - groups. + Register a handler. + + TL;DR: Order and priority counts. 0 or 1 handlers per group will be + used. + + A handler must be an instance of a subclass of + telegram.ext.Handler. All handlers are organized in groups with a + numeric value. The default group is 0. All groups will be evaluated for + handling an update, but only 0 or 1 handler per group will be used. + + The priority/order of handlers is determined as follows: + + * Priority of the group (lower group number == higher priority) + + * The first handler in a group which should handle an update will be + used. Other handlers from the group will not be used. The order in + which handlers were added to the group defines the priority. Args: handler (Handler): A Handler instance - group (optional[object]): The group identifier. Default is 0 + group (Optional[int]): The group identifier. Default is 0 """ if not isinstance(handler, Handler): - raise TypeError('Handler is no instance of telegram.ext.Handler') + raise TypeError( + 'handler is not an instance of {0}'.format(Handler.__name__)) + if not isinstance(group, int): + raise TypeError('group is not int') if group not in self.handlers: self.handlers[group] = list() + self.groups.append(group) + self.groups = sorted(self.groups) self.handlers[group].append(handler) @@ -236,6 +249,9 @@ class Dispatcher(object): """ if handler in self.handlers[group]: self.handlers[group].remove(handler) + if not self.handlers[group]: + del self.handlers[group] + self.groups.remove(group) def addErrorHandler(self, callback): """ diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py deleted file mode 100644 index 8bebebe6e..000000000 --- a/telegram/ext/filters.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2016 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. - -""" This module contains the filters used by the MessageHandler class """ - -TEXT, AUDIO, DOCUMENT, PHOTO, STICKER, VIDEO, VOICE, CONTACT, LOCATION, \ - VENUE, STATUS_UPDATE = range(11) diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 10646093b..a2f6dead2 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -46,6 +46,9 @@ class Handler(object): Args: update (object): The update to be tested + + Returns: + bool """ raise NotImplementedError diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 7aba49d61..a8c461e10 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -22,11 +22,7 @@ import logging import time from threading import Thread, Lock - -try: - from queue import PriorityQueue -except ImportError: - from Queue import PriorityQueue +from queue import PriorityQueue class JobQueue(object): diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 1dddf56bd..135a08f07 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -22,7 +22,71 @@ from .handler import Handler from telegram import Update -from .filters import * # flake8: noqa + +class Filters(object): + """ + Convenient namespace (class) & methods for the filter funcs of the + MessageHandler class. + """ + + @staticmethod + def text(update): + return update.message.text and not update.message.text.startswith('/') + + @staticmethod + def command(update): + return update.message.text and update.message.text.startswith('/') + + @staticmethod + def audio(update): + return bool(update.message.audio) + + @staticmethod + def document(update): + return bool(update.message.document) + + @staticmethod + def photo(update): + return bool(update.message.photo) + + @staticmethod + def sticker(update): + return bool(update.message.sticker) + + @staticmethod + def video(update): + return bool(update.message.video) + + @staticmethod + def voice(update): + return bool(update.message.voice) + + @staticmethod + def contact(update): + return bool(update.message.contact) + + @staticmethod + def location(update): + return bool(update.message.location) + + @staticmethod + def venue(update): + return bool(update.message.venue) + + @staticmethod + def status_update(update): + return bool( + update.message.new_chat_member or + update.message.left_chat_member or + update.message.new_chat_title or + update.message.new_chat_photo or + update.message.delete_chat_photo or + update.message.group_chat_created or + update.message.supergroup_chat_created or + update.message.channel_chat_created or + update.message.migrate_to_chat_id or + update.message.migrate_from_chat_id or + update.message.pinned_message) class MessageHandler(Handler): @@ -32,8 +96,10 @@ class MessageHandler(Handler): updates. Args: - filters (list): A list of filters defined in ``telegram.ext.filters``. - All messages that match at least one of those filters will be + filters (list[function]): A list of filter functions. Standard filters + can be found in the Filters class above. + | Each `function` takes ``Update`` as arg and returns ``bool``. + | All messages that match at least one of those filters will be accepted. If ``bool(filters)`` evaluates to ``False``, messages are not filtered. callback (function): A function that takes ``bot, update`` as @@ -49,36 +115,14 @@ class MessageHandler(Handler): self.filters = filters def checkUpdate(self, update): - filters = self.filters if isinstance(update, Update) and update.message: - message = update.message - return (not filters or # If filters is empty, accept all messages - TEXT in filters and message.text and - not message.text.startswith('/') or - AUDIO in filters and message.audio or - DOCUMENT in filters and message.document or - PHOTO in filters and message.photo or - STICKER in filters and message.sticker or - VIDEO in filters and message.video or - VOICE in filters and message.voice or - CONTACT in filters and message.contact or - LOCATION in filters and message.location or - VENUE in filters and message.venue or - STATUS_UPDATE in filters and ( - message.new_chat_member or - message.left_chat_member or - message.new_chat_title or - message.new_chat_photo or - message.delete_chat_photo or - message.group_chat_created or - message.supergroup_chat_created or - message.channel_chat_created or - message.migrate_to_chat_id or - message.migrate_from_chat_id or - message.pinned_message) - ) + if not self.filters: + res = True + else: + res = any(func(update) for func in self.filters) else: - return False + res = False + return res def handleUpdate(self, update, dispatcher): optional_args = self.collectOptionalArgs(dispatcher) diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index ec4da53f4..c3a7f976b 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -21,6 +21,8 @@ import re +from future.utils import string_types + from .handler import Handler from telegram import Update @@ -52,7 +54,7 @@ class RegexHandler(Handler): pass_groupdict=False, pass_update_queue=False): super(RegexHandler, self).__init__(callback, pass_update_queue) - if isinstance(pattern, str): + if isinstance(pattern, string_types): pattern = re.compile(pattern) self.pattern = pattern diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index c064ff8cb..6ea748fd2 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -21,6 +21,8 @@ import re +from future.utils import string_types + from .handler import Handler @@ -51,7 +53,7 @@ class StringRegexHandler(Handler): pass_groupdict=False, pass_update_queue=False): super(StringRegexHandler, self).__init__(callback, pass_update_queue) - if isinstance(pattern, str): + if isinstance(pattern, string_types): pattern = re.compile(pattern) self.pattern = pattern @@ -59,11 +61,8 @@ class StringRegexHandler(Handler): self.pass_groupdict = pass_groupdict def checkUpdate(self, update): - if isinstance(update, str): - match = re.match(self.pattern, update) - return bool(match) - else: - return False + return isinstance(update, string_types) and bool( + re.match(self.pattern, update)) def handleUpdate(self, update, dispatcher): optional_args = self.collectOptionalArgs(dispatcher) diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index 705c0b195..ea1e1737a 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -28,12 +28,7 @@ from threading import Thread, Lock, current_thread, Event from time import sleep import subprocess from signal import signal, SIGINT, SIGTERM, SIGABRT - -# Adjust for differences in Python versions -try: - from queue import Queue # flake8: noqa -except ImportError: - from Queue import Queue # flake8: noqa +from queue import Queue from telegram import Bot, TelegramError, NullHandler from telegram.ext import dispatcher, Dispatcher, JobQueue diff --git a/telegram/utils/validate.py b/telegram/utils/validate.py index 834cf9b17..d8629750a 100644 --- a/telegram/utils/validate.py +++ b/telegram/utils/validate.py @@ -21,24 +21,6 @@ from telegram.error import InvalidToken -try: - type(basestring) -except NameError: - basestring = str - - -def validate_string(arg, name): - """ - Validate a string argument. Raises a ValueError if `arg` is neither an - instance of basestring (Python 2) or str (Python 3) nor None. - - Args: - arg (basestring): The value to be tested - name (str): The name of the argument, for the error message - """ - if not isinstance(arg, basestring) and arg is not None: - raise ValueError(name + ' is not a string') - def validate_token(token): """a very basic validation on token""" diff --git a/tests/test_updater.py b/tests/test_updater.py index b981aa52f..426355f8b 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -144,8 +144,8 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addRemoveTelegramMessageHandler(self): self._setup_updater('Test') d = self.updater.dispatcher - from telegram.ext import filters - handler = MessageHandler([filters.TEXT], self.telegramHandlerTest) + from telegram.ext import Filters + handler = MessageHandler([Filters.text], self.telegramHandlerTest) d.addHandler(handler) self.updater.start_polling(0.01) sleep(.1)