Implement webhook listener server

This commit is contained in:
Jannes Höke 2015-11-16 13:05:57 +01:00
parent ba3d174fde
commit 396dc6cd3c
2 changed files with 136 additions and 6 deletions

View file

@ -5,18 +5,24 @@ This module contains the class BotEventHandler, which tries to make creating
Telegram Bots intuitive!
"""
import logging
import sys
import ssl
from threading import Thread
from telegram import (Bot, TelegramError, broadcaster, Broadcaster,
NullHandler)
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
from time import sleep
# Adjust for differences in Python versions
if sys.version_info.major is 2:
try:
from Queue import Queue
elif sys.version_info.major is 3:
except ImportError:
from queue import Queue
try:
import BaseHTTPServer
except ImportError:
import http.server as BaseHTTPServer
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
@ -53,8 +59,9 @@ class BotEventHandler:
workers=workers)
self.logger = logging.getLogger(__name__)
self.running = False
self.httpd = None
def start(self, poll_interval=1.0, timeout=10, network_delay=2):
def start_polling(self, poll_interval=1.0, timeout=10, network_delay=2):
"""
Starts polling updates from Telegram.
@ -71,7 +78,8 @@ class BotEventHandler:
# Create Thread objects
broadcaster_thread = Thread(target=self.broadcaster.start,
name="broadcaster")
event_handler_thread = Thread(target=self.__start, name="eventhandler",
event_handler_thread = Thread(target=self._start_polling,
name="eventhandler",
args=(poll_interval, timeout,
network_delay))
@ -84,7 +92,38 @@ class BotEventHandler:
# Return the update queue so the main thread can insert updates
return self.update_queue
def __start(self, poll_interval, timeout, network_delay):
def start_webhook(self, host, port, cert, key, listen='0.0.0.0'):
"""
Starts polling updates from Telegram.
Args:
host (str): Hostname or IP of the bot
port (int): Port the bot should be listening on
cert (str): Path to the SSL certificate file
key (str): Path to the SSL key file
listen (Optional[str]): IP-Address to listen on
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_webhook,
name="eventhandler",
args=(host, port, cert, key, listen))
self.running = 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_polling(self, poll_interval, timeout, network_delay):
"""
Thread target of thread 'eventhandler'. Runs in background, pulls
updates from Telegram and inserts them in the update queue of the
@ -94,6 +133,9 @@ class BotEventHandler:
current_interval = poll_interval
self.logger.info('Event Handler thread started')
# Remove webhook
self.bot.setWebhook(webhook_url=None)
while self.running:
try:
updates = self.bot.getUpdates(self.last_update_id,
@ -125,12 +167,42 @@ class BotEventHandler:
self.logger.info('Event Handler thread stopped')
def _start_webhook(self, host, port, cert, key, listen):
self.logger.info('Event Handler thread started')
url_base = "https://%s:%d" % (host, port)
url_path = "/%s" % self.bot.token
# Remove webhook
self.bot.setWebhook(webhook_url=None)
# Set webhook
self.bot.setWebhook(webhook_url=url_base + url_path,
certificate=open(cert, 'rb'))
# Start server
self.httpd = WebhookServer((listen, port), WebhookHandler,
self.update_queue, url_path)
self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
certfile=cert,
keyfile=key,
server_side=True)
self.httpd.serve_forever()
self.logger.info('Event Handler thread stopped')
def stop(self):
"""
Stops the polling thread and the broadcaster
"""
self.logger.info('Stopping Event Handler and Broadcaster...')
self.running = False
if self.httpd:
self.logger.info(
'BETA: Webhook Server will stop after next message')
self.httpd.shutdown()
self.broadcaster.stop()
while broadcaster.running_async > 0:
sleep(1)

View file

@ -0,0 +1,58 @@
import logging
from telegram import Update, NullHandler
from future.utils import bytes_to_native_str as n
import json
try:
import BaseHTTPServer
except ImportError:
import http.server as BaseHTTPServer
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
class WebhookServer(BaseHTTPServer.HTTPServer):
def __init__(self, server_address, RequestHandlerClass, update_queue,
webhook_path):
super().__init__(server_address, RequestHandlerClass)
self.update_queue = update_queue
self.webhook_path = webhook_path
# WebhookHandler, process webhook calls
# Based on: https://github.com/eternnoir/pyTelegramBotAPI/blob/master/
# examples/webhook_examples/webhook_cpython_echo_bot.py
class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler):
server_version = "WebhookHandler/1.0"
def __init__(self, request, client_address, server):
super().__init__(request, client_address, server)
self.logger = logging.getLogger(__name__)
def do_HEAD(self):
self.send_response(200)
self.end_headers()
def do_GET(self):
self.send_response(200)
self.end_headers()
def do_POST(self):
if self.path == self.server.webhook_path and \
'content-type' in self.headers and \
'content-length' in self.headers and \
self.headers['content-type'] == 'application/json':
json_string = \
n(self.rfile.read(int(self.headers['content-length'])))
self.send_response(200)
self.end_headers()
update = Update.de_json(json.loads(json_string))
self.server.update_queue.put(update)
else:
self.send_error(403)
self.end_headers()