mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-23 06:50:29 +01:00
initial commit for BotEventHandler and Broadcaster
This commit is contained in:
parent
93afd3eabe
commit
ef99bab435
2 changed files with 554 additions and 0 deletions
102
telegram/boteventhandler.py
Normal file
102
telegram/boteventhandler.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains the class BotEventHandler, which tries to make creating
|
||||||
|
Telegram Bots intuitive!
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from threading import Thread
|
||||||
|
from telegram import (Bot, TelegramError, TelegramObject, Broadcaster)
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
# Adjust for differences in Python versions
|
||||||
|
if sys.version_info.major is 2:
|
||||||
|
from Queue import Queue
|
||||||
|
elif sys.version_info.major is 3:
|
||||||
|
from queue import Queue
|
||||||
|
|
||||||
|
class BotEventHandler(TelegramObject):
|
||||||
|
"""
|
||||||
|
This class provides a frontend to telegram.Bot to the programmer, so they
|
||||||
|
can focus on coding the bot. I also runs in a separate thread, so the user
|
||||||
|
can interact with the bot, for example on the command line. It supports
|
||||||
|
Handlers for different kinds of data: Updates from Telegram, basic text
|
||||||
|
commands and even arbitrary types.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
Args:
|
||||||
|
token (str): The bots token given by the @BotFather
|
||||||
|
**kwargs: Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
base_url (Optional[str]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, token, base_url=None):
|
||||||
|
self.bot = Bot(token, base_url)
|
||||||
|
self.update_queue = Queue()
|
||||||
|
self.last_update_id = 0
|
||||||
|
self.broadcaster = Broadcaster(self.bot, self.update_queue)
|
||||||
|
|
||||||
|
def start(self, poll_interval=1.0):
|
||||||
|
"""
|
||||||
|
Starts polling updates from Telegram.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
poll_interval (Optional[float]): Time to wait between polling
|
||||||
|
updates from Telegram in seconds. Default is 1.0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Queue: The update queue that can be filled from the main thread
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create Thread objects
|
||||||
|
broadcaster_thread = Thread(target=self.broadcaster.start,
|
||||||
|
name="broadcaster")
|
||||||
|
event_handler_thread = Thread(target=self.__start, name="eventhandler",
|
||||||
|
args=(poll_interval,))
|
||||||
|
|
||||||
|
# Set threads as daemons so they'll stop if the main thread stops
|
||||||
|
broadcaster_thread.daemon = True
|
||||||
|
event_handler_thread.daemon = True
|
||||||
|
|
||||||
|
# Start threads
|
||||||
|
broadcaster_thread.start()
|
||||||
|
event_handler_thread.start()
|
||||||
|
|
||||||
|
# Return the update queue so the main thread can insert updates
|
||||||
|
return self.update_queue
|
||||||
|
|
||||||
|
def __start(self, poll_interval):
|
||||||
|
"""
|
||||||
|
Thread target of thread 'eventhandler'. Runs in background, pulls
|
||||||
|
updates from Telegram and inserts them in the update queue of the
|
||||||
|
Broadcaster.
|
||||||
|
"""
|
||||||
|
|
||||||
|
current_interval = poll_interval
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
updates = self.bot.getUpdates(self.last_update_id)
|
||||||
|
for update in updates:
|
||||||
|
self.update_queue.put(update)
|
||||||
|
self.last_update_id = update.update_id + 1
|
||||||
|
current_interval = poll_interval
|
||||||
|
sleep(current_interval)
|
||||||
|
except TelegramError as te:
|
||||||
|
# Put the error into the update queue and let the Broadcaster
|
||||||
|
# broadcast it
|
||||||
|
self.update_queue.put(te)
|
||||||
|
sleep(current_interval)
|
||||||
|
|
||||||
|
# increase waiting times on subsequent errors up to 30secs
|
||||||
|
if current_interval < 30:
|
||||||
|
current_interval += current_interval / 2
|
||||||
|
if current_interval > 30:
|
||||||
|
current_interval = 30
|
452
telegram/broadcaster.py
Normal file
452
telegram/broadcaster.py
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module contains the Broadcaster class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from telegram import (TelegramError, TelegramObject, Update)
|
||||||
|
|
||||||
|
class Broadcaster(TelegramObject):
|
||||||
|
"""
|
||||||
|
This class broadcasts all kinds of updates to its registered handlers.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (telegram.Bot): The bot object that should be passed to the handlers
|
||||||
|
update_queue (queue.Queue): The synchronized queue that will contain the
|
||||||
|
updates
|
||||||
|
"""
|
||||||
|
def __init__(self, bot, update_queue):
|
||||||
|
self.bot = bot
|
||||||
|
self.update_queue = update_queue
|
||||||
|
self.telegram_message_handlers = []
|
||||||
|
self.telegram_command_handlers = {}
|
||||||
|
self.telegram_regex_handlers = {}
|
||||||
|
self.string_regex_handlers = {}
|
||||||
|
self.string_command_handlers = {}
|
||||||
|
self.type_handlers = {}
|
||||||
|
self.unknown_telegram_command_handlers = []
|
||||||
|
self.unknown_string_command_handlers = []
|
||||||
|
self.error_handlers = []
|
||||||
|
|
||||||
|
# Add Handlers
|
||||||
|
def addTelegramMessageHandler(self, handler):
|
||||||
|
"""
|
||||||
|
Registers a message handler in the Broadcaster.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (function): A function that takes (Bot, Update) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.telegram_message_handlers.append(handler)
|
||||||
|
|
||||||
|
def addTelegramCommandHandler(self, command, handler):
|
||||||
|
"""
|
||||||
|
Registers a command handler in the Broadcaster.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command keyword that this handler should be
|
||||||
|
listening to.
|
||||||
|
handler (function): A function that takes (Bot, Update) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if command not in self.telegram_command_handlers:
|
||||||
|
self.telegram_command_handlers[command] = []
|
||||||
|
|
||||||
|
self.telegram_command_handlers[command].append(handler)
|
||||||
|
|
||||||
|
def addTelegramRegexHandler(self, matcher, handler):
|
||||||
|
"""
|
||||||
|
Registers a regex handler in the Broadcaster. If handlers will be called
|
||||||
|
if matcher.math(update.message.text) is True.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
matcher (str): A compiled regex object that matches on messages that
|
||||||
|
handler should be listening to
|
||||||
|
handler (function): A function that takes (Bot, Update) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if matcher not in self.telegram_regex_handlers:
|
||||||
|
self.telegram_regex_handlers[matcher] = []
|
||||||
|
|
||||||
|
self.telegram_regex_handlers[matcher].append(handler)
|
||||||
|
|
||||||
|
def addStringCommandHandler(self, command, handler):
|
||||||
|
"""
|
||||||
|
Registers a string-command handler in the Broadcaster.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command keyword that this handler should be
|
||||||
|
listening to.
|
||||||
|
handler (function): A function that takes (Bot, str) as arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if command not in self.string_command_handlers:
|
||||||
|
self.string_command_handlers[command] = []
|
||||||
|
|
||||||
|
self.string_command_handlers[command].append(handler)
|
||||||
|
|
||||||
|
def addStringRegexHandler(self, matcher, handler):
|
||||||
|
"""
|
||||||
|
Registers a regex handler in the Broadcaster. If handlers will be called
|
||||||
|
if matcher.math(string) is True.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
matcher (str): A compiled regex object that matches on the string
|
||||||
|
input that handler should be listening to
|
||||||
|
handler (function): A function that takes (Bot, Update) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if matcher not in self.string_regex_handlers:
|
||||||
|
self.string_regex_handlers[matcher] = []
|
||||||
|
|
||||||
|
self.string_regex_handlers[matcher].append(handler)
|
||||||
|
|
||||||
|
def addUnknownTelegramCommandHandler(self, handler):
|
||||||
|
"""
|
||||||
|
Registers a command handler in the Broadcaster, that will receive all
|
||||||
|
commands that have no associated handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (function): A function that takes (Bot, Update) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.unknown_telegram_command_handlers.append(handler)
|
||||||
|
|
||||||
|
def addUnknownStringCommandHandler(self, handler):
|
||||||
|
"""
|
||||||
|
Registers a string-command handler in the Broadcaster, that will receive
|
||||||
|
all commands that have no associated handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (function): A function that takes (Bot, str) as arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.unknown_string_command_handlers.append(handler)
|
||||||
|
|
||||||
|
def addErrorHandler(self, handler):
|
||||||
|
"""
|
||||||
|
Registers an error handler in the Broadcaster.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (function): A function that takes (Bot, TelegramError) as
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.error_handlers.append(handler)
|
||||||
|
|
||||||
|
def addTypeHandler(self, the_type, handler):
|
||||||
|
"""
|
||||||
|
Registers a type handler in the Broadcaster. This allows you to send
|
||||||
|
any type of object into the update queue.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
the_type (type): The type this handler should listen to
|
||||||
|
handler (function): A function that takes (Bot, type) as arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if the_type not in self.type_handlers:
|
||||||
|
self.type_handlers[the_type] = []
|
||||||
|
|
||||||
|
self.type_handlers[the_type].append(handler)
|
||||||
|
|
||||||
|
# Remove Handlers
|
||||||
|
def removeTelegramMessageHandler(self, handler):
|
||||||
|
"""
|
||||||
|
De-registers a message handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if handler in self.telegram_message_handlers:
|
||||||
|
self.telegram_message_handlers.remove(handler)
|
||||||
|
|
||||||
|
def removeTelegramCommandHandler(self, command, handler):
|
||||||
|
"""
|
||||||
|
De-registers a command handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if command in self.telegram_command_handlers \
|
||||||
|
and handler in self.telegram_command_handlers[command]:
|
||||||
|
self.telegram_command_handlers[command].remove(handler)
|
||||||
|
|
||||||
|
def removeTelegramRegexHandler(self, matcher, handler):
|
||||||
|
"""
|
||||||
|
De-registers a regex handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
matcher (str): The regex matcher object
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if matcher in self.telegram_regex_handlers \
|
||||||
|
and handler in self.telegram_regex_handlers[matcher]:
|
||||||
|
self.telegram_regex_handlers[matcher].remove(handler)
|
||||||
|
|
||||||
|
def removeStringCommandHandler(self, command, handler):
|
||||||
|
"""
|
||||||
|
De-registers a string-command handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if command in self.string_command_handlers \
|
||||||
|
and handler in self.string_command_handlers[command]:
|
||||||
|
self.string_command_handlers[command].remove(handler)
|
||||||
|
|
||||||
|
def removeStringRegexHandler(self, matcher, handler):
|
||||||
|
"""
|
||||||
|
De-registers a regex handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
matcher (str): The regex matcher object
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if matcher in self.string_regex_handlers \
|
||||||
|
and handler in self.string_regex_handlers[matcher]:
|
||||||
|
self.string_regex_handlers[matcher].remove(handler)
|
||||||
|
|
||||||
|
def removeUnknownTelegramCommandHandler(self, handler):
|
||||||
|
"""
|
||||||
|
De-registers an unknown-command handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if handler in self.unknown_telegram_command_handlers:
|
||||||
|
self.unknown_telegram_command_handlers.remove(handler)
|
||||||
|
|
||||||
|
def removeUnknownStringCommandHandler(self, handler):
|
||||||
|
"""
|
||||||
|
De-registers an unknown-command handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if handler in self.unknown_string_command_handlers:
|
||||||
|
self.unknown_string_command_handlers.remove(handler)
|
||||||
|
|
||||||
|
def removeErrorHandler(self, handler):
|
||||||
|
"""
|
||||||
|
De-registers an error handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if handler in self.error_handlers:
|
||||||
|
self.error_handlers.remove(handler)
|
||||||
|
|
||||||
|
def removeTypeHandler(self, the_type, handler):
|
||||||
|
"""
|
||||||
|
De-registers a type handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
if the_type in self.type_handlers \
|
||||||
|
and handler in self.type_handlers[the_type]:
|
||||||
|
self.type_handlers[the_type].remove(handler)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Thread target of thread 'broadcaster'. Runs in background and processes
|
||||||
|
the update queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Pop update from update queue.
|
||||||
|
# Blocks if no updates are available.
|
||||||
|
update = self.update_queue.get()
|
||||||
|
self.processUpdate(update)
|
||||||
|
|
||||||
|
# Broadcast any errors
|
||||||
|
except TelegramError as te:
|
||||||
|
self.broadcastError(te)
|
||||||
|
|
||||||
|
def processUpdate(self, update):
|
||||||
|
"""
|
||||||
|
Processes a single update.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update (any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
handled = False
|
||||||
|
|
||||||
|
# Custom type handlers
|
||||||
|
for t in self.type_handlers:
|
||||||
|
if isinstance(update, t):
|
||||||
|
self.broadcastType(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# string update
|
||||||
|
if type(update) is str and update.startswith('/'):
|
||||||
|
self.broadcastStringCommand(update)
|
||||||
|
handled = True
|
||||||
|
elif type(update) is str:
|
||||||
|
self.broadcastStringRegex(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# An error happened while polling
|
||||||
|
if isinstance(update, TelegramError):
|
||||||
|
self.broadcastError(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# Telegram update (regex)
|
||||||
|
if isinstance(update, Update):
|
||||||
|
self.broadcastTelegramRegex(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# Telegram update (command)
|
||||||
|
if isinstance(update, Update) \
|
||||||
|
and update.message.text.startswith('/'):
|
||||||
|
self.broadcastTelegramCommand(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# Telegram update (message)
|
||||||
|
elif isinstance(update, Update):
|
||||||
|
self.broadcastTelegramMessage(update)
|
||||||
|
handled = True
|
||||||
|
|
||||||
|
# Update not recognized
|
||||||
|
if not handled:
|
||||||
|
self.broadcastError(TelegramError(
|
||||||
|
"Received update of unknown type %s" % type(update)))
|
||||||
|
|
||||||
|
def broadcastTelegramCommand(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update that contains a command.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command keyword
|
||||||
|
update (telegram.Update): The Telegram update that contains the
|
||||||
|
command
|
||||||
|
"""
|
||||||
|
|
||||||
|
command = update.message.text.split(' ')[0][1:].split('@')[0]
|
||||||
|
|
||||||
|
if command in self.telegram_command_handlers:
|
||||||
|
self.broadcastTo(self.telegram_command_handlers[command], update)
|
||||||
|
else:
|
||||||
|
self.broadcastTo(self.unknown_telegram_command_handlers, update)
|
||||||
|
|
||||||
|
def broadcastTelegramRegex(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update to all regex handlers that match the message string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command keyword
|
||||||
|
update (telegram.Update): The Telegram update that contains the
|
||||||
|
command
|
||||||
|
"""
|
||||||
|
|
||||||
|
matching_handlers = []
|
||||||
|
|
||||||
|
for matcher in self.telegram_regex_handlers:
|
||||||
|
if matcher.match(update.message.text):
|
||||||
|
for handler in self.telegram_regex_handlers[matcher]:
|
||||||
|
matching_handlers.append(handler)
|
||||||
|
|
||||||
|
self.broadcastTo(matching_handlers, update)
|
||||||
|
|
||||||
|
def broadcastStringCommand(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts a string-update that contains a command.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update (str): The string input
|
||||||
|
"""
|
||||||
|
|
||||||
|
command = update.split(' ')[0][1:]
|
||||||
|
|
||||||
|
if command in self.string_command_handlers:
|
||||||
|
self.broadcastTo(self.string_command_handlers[command], update)
|
||||||
|
else:
|
||||||
|
self.broadcastTo(self.unknown_string_command_handlers, update)
|
||||||
|
|
||||||
|
def broadcastStringRegex(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update to all string regex handlers that match the string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (str): The command keyword
|
||||||
|
update (telegram.Update): The Telegram update that contains the
|
||||||
|
command
|
||||||
|
"""
|
||||||
|
|
||||||
|
matching_handlers = []
|
||||||
|
|
||||||
|
for matcher in self.string_regex_handlers:
|
||||||
|
if matcher.match(update):
|
||||||
|
for handler in self.string_regex_handlers[matcher]:
|
||||||
|
matching_handlers.append(handler)
|
||||||
|
|
||||||
|
self.broadcastTo(matching_handlers, update)
|
||||||
|
|
||||||
|
def broadcastType(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update of any type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update (any): The update
|
||||||
|
"""
|
||||||
|
|
||||||
|
for t in self.type_handlers:
|
||||||
|
if isinstance(update, t):
|
||||||
|
self.broadcastTo(self.type_handlers[t], update)
|
||||||
|
else:
|
||||||
|
self.broadcastError(TelegramError(
|
||||||
|
"Received update of unknown type %s" % type(update)))
|
||||||
|
|
||||||
|
def broadcastTelegramMessage(self, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update that contains a regular message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update (telegram.Update): The Telegram update that contains the
|
||||||
|
message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.broadcastTo(self.telegram_message_handlers, update)
|
||||||
|
|
||||||
|
def broadcastError(self, error):
|
||||||
|
"""
|
||||||
|
Broadcasts an error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error (telegram.TelegramError): The Telegram error that was raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for handler in self.error_handlers:
|
||||||
|
handler(self.bot, error)
|
||||||
|
|
||||||
|
def broadcastTo(self, handlers, update):
|
||||||
|
"""
|
||||||
|
Broadcasts an update to a list of handlers.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handlers (list): A list of handler-functions.
|
||||||
|
update (any): The update to be broadcasted
|
||||||
|
"""
|
||||||
|
|
||||||
|
for handler in handlers:
|
||||||
|
handler(self.bot, update)
|
Loading…
Reference in a new issue