2021-01-30 14:15:39 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# A library that provides a Python interface to the Telegram Bot API
|
2022-01-03 08:15:18 +01:00
|
|
|
# Copyright (C) 2015-2022
|
2021-01-30 14:15:39 +01:00
|
|
|
# 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/].
|
2021-10-08 08:17:00 +02:00
|
|
|
# pylint: disable=missing-module-docstring
|
2022-04-24 12:38:09 +02:00
|
|
|
import asyncio
|
2021-01-30 14:15:39 +01:00
|
|
|
import logging
|
2022-04-24 12:38:09 +02:00
|
|
|
from http import HTTPStatus
|
2021-01-30 14:15:39 +01:00
|
|
|
from ssl import SSLContext
|
2022-04-24 12:38:09 +02:00
|
|
|
from types import TracebackType
|
|
|
|
from typing import TYPE_CHECKING, Optional, Type
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
import tornado.web
|
|
|
|
from tornado.httpserver import HTTPServer
|
|
|
|
|
|
|
|
from telegram import Update
|
2022-05-05 09:27:54 +02:00
|
|
|
from telegram.ext._extbot import ExtBot
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from telegram import Bot
|
|
|
|
|
|
|
|
try:
|
|
|
|
import ujson as json
|
|
|
|
except ImportError:
|
|
|
|
import json # type: ignore[no-redef]
|
|
|
|
|
|
|
|
|
|
|
|
class WebhookServer:
|
2022-04-24 12:38:09 +02:00
|
|
|
"""Thin wrapper around ``tornado.httpserver.HTTPServer``."""
|
|
|
|
|
2021-05-29 16:18:16 +02:00
|
|
|
__slots__ = (
|
2022-04-24 12:38:09 +02:00
|
|
|
'_http_server',
|
2021-05-29 16:18:16 +02:00
|
|
|
'listen',
|
|
|
|
'port',
|
2022-04-24 12:38:09 +02:00
|
|
|
'_logger',
|
2021-05-29 16:18:16 +02:00
|
|
|
'is_running',
|
2022-04-24 12:38:09 +02:00
|
|
|
'_server_lock',
|
|
|
|
'_shutdown_lock',
|
2021-05-29 16:18:16 +02:00
|
|
|
)
|
|
|
|
|
2021-01-30 14:15:39 +01:00
|
|
|
def __init__(
|
2022-04-24 12:38:09 +02:00
|
|
|
self, listen: str, port: int, webhook_app: 'WebhookAppClass', ssl_ctx: Optional[SSLContext]
|
2021-01-30 14:15:39 +01:00
|
|
|
):
|
2022-04-24 12:38:09 +02:00
|
|
|
self._http_server = HTTPServer(webhook_app, ssl_options=ssl_ctx)
|
2021-01-30 14:15:39 +01:00
|
|
|
self.listen = listen
|
|
|
|
self.port = port
|
2022-04-24 12:38:09 +02:00
|
|
|
self._logger = logging.getLogger(__name__)
|
2021-01-30 14:15:39 +01:00
|
|
|
self.is_running = False
|
2022-04-24 12:38:09 +02:00
|
|
|
self._server_lock = asyncio.Lock()
|
|
|
|
self._shutdown_lock = asyncio.Lock()
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def serve_forever(self, ready: asyncio.Event = None) -> None:
|
|
|
|
async with self._server_lock:
|
|
|
|
self._http_server.listen(self.port, address=self.listen)
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
self.is_running = True
|
2021-01-30 14:15:39 +01:00
|
|
|
if ready is not None:
|
|
|
|
ready.set()
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
self._logger.debug('Webhook Server started.')
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def shutdown(self) -> None:
|
|
|
|
async with self._shutdown_lock:
|
2021-01-30 14:15:39 +01:00
|
|
|
if not self.is_running:
|
2022-04-24 12:38:09 +02:00
|
|
|
self._logger.debug('Webhook Server is already shut down. Returning')
|
2021-01-30 14:15:39 +01:00
|
|
|
return
|
2022-04-24 12:38:09 +02:00
|
|
|
self.is_running = False
|
|
|
|
self._http_server.stop()
|
|
|
|
await self._http_server.close_all_connections()
|
|
|
|
self._logger.debug('Webhook Server stopped')
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
class WebhookAppClass(tornado.web.Application):
|
2022-04-24 12:38:09 +02:00
|
|
|
"""Application used in the Webserver"""
|
|
|
|
|
|
|
|
def __init__(self, webhook_path: str, bot: 'Bot', update_queue: asyncio.Queue):
|
2021-01-30 14:15:39 +01:00
|
|
|
self.shared_objects = {"bot": bot, "update_queue": update_queue}
|
2022-04-24 12:38:09 +02:00
|
|
|
handlers = [(rf"{webhook_path}/?", TelegramHandler, self.shared_objects)] # noqa
|
2021-03-13 16:21:03 +01:00
|
|
|
tornado.web.Application.__init__(self, handlers) # type: ignore
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
def log_request(self, handler: tornado.web.RequestHandler) -> None:
|
|
|
|
"""Overrides the default implementation since we have our own logging setup."""
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
|
2021-10-08 08:17:00 +02:00
|
|
|
# pylint: disable=abstract-method
|
2022-04-24 12:38:09 +02:00
|
|
|
class TelegramHandler(tornado.web.RequestHandler):
|
|
|
|
"""Handler that processes incoming requests from Telegram"""
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
__slots__ = ('bot', 'update_queue', '_logger')
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
SUPPORTED_METHODS = ("POST",) # type: ignore[assignment]
|
|
|
|
|
|
|
|
def initialize(self, bot: 'Bot', update_queue: asyncio.Queue) -> None:
|
|
|
|
"""Initialize for each request - that's the interface provided by tornado"""
|
2021-10-08 08:17:00 +02:00
|
|
|
# pylint: disable=attribute-defined-outside-init
|
2021-01-30 14:15:39 +01:00
|
|
|
self.bot = bot
|
|
|
|
self.update_queue = update_queue
|
2022-04-24 12:38:09 +02:00
|
|
|
self._logger = logging.getLogger(__name__)
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
def set_default_headers(self) -> None:
|
2022-04-24 12:38:09 +02:00
|
|
|
"""Sets default headers"""
|
2021-01-30 14:15:39 +01:00
|
|
|
self.set_header("Content-Type", 'application/json; charset="utf-8"')
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def post(self) -> None:
|
|
|
|
"""Handle incoming POST request"""
|
|
|
|
self._logger.debug('Webhook triggered')
|
2021-01-30 14:15:39 +01:00
|
|
|
self._validate_post()
|
2022-04-24 12:38:09 +02:00
|
|
|
|
2021-01-30 14:15:39 +01:00
|
|
|
json_string = self.request.body.decode()
|
|
|
|
data = json.loads(json_string)
|
2022-04-24 12:38:09 +02:00
|
|
|
self.set_status(HTTPStatus.OK)
|
|
|
|
self._logger.debug('Webhook received data: %s', json_string)
|
|
|
|
|
|
|
|
try:
|
|
|
|
update = Update.de_json(data, self.bot)
|
|
|
|
except Exception as exc:
|
|
|
|
self._logger.critical(
|
|
|
|
'Something went wrong processing the data received from Telegram. '
|
|
|
|
'Received data was *not* processed!',
|
|
|
|
exc_info=exc,
|
|
|
|
)
|
|
|
|
|
2021-01-30 14:15:39 +01:00
|
|
|
if update:
|
2022-04-24 12:38:09 +02:00
|
|
|
self._logger.debug('Received Update with ID %d on Webhook', update.update_id)
|
|
|
|
|
2021-06-06 11:48:48 +02:00
|
|
|
# handle arbitrary callback data, if necessary
|
|
|
|
if isinstance(self.bot, ExtBot):
|
|
|
|
self.bot.insert_callback_data(update)
|
2022-04-24 12:38:09 +02:00
|
|
|
|
|
|
|
await self.update_queue.put(update)
|
2021-01-30 14:15:39 +01:00
|
|
|
|
|
|
|
def _validate_post(self) -> None:
|
2022-04-24 12:38:09 +02:00
|
|
|
"""Only accept requests with content type JSON"""
|
2021-01-30 14:15:39 +01:00
|
|
|
ct_header = self.request.headers.get("Content-Type", None)
|
|
|
|
if ct_header != 'application/json':
|
2022-04-24 12:38:09 +02:00
|
|
|
raise tornado.web.HTTPError(HTTPStatus.FORBIDDEN)
|
2021-01-30 14:15:39 +01:00
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
def log_exception(
|
|
|
|
self,
|
|
|
|
typ: Optional[Type[BaseException]],
|
|
|
|
value: Optional[BaseException],
|
|
|
|
tb: Optional[TracebackType],
|
|
|
|
) -> None:
|
|
|
|
"""Override the default logging and instead use our custom logging."""
|
|
|
|
self._logger.debug(
|
|
|
|
"%s - %s",
|
2021-01-30 14:15:39 +01:00
|
|
|
self.request.remote_ip,
|
2022-04-24 12:38:09 +02:00
|
|
|
"Exception in TelegramHandler",
|
|
|
|
exc_info=(typ, value, tb) if typ and value and tb else value,
|
2021-01-30 14:15:39 +01:00
|
|
|
)
|