a begin that works

This commit is contained in:
2025-07-19 16:14:29 +02:00
parent d39a8fe644
commit 5790346ff9
2085 changed files with 374513 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains helper functions related to parsing updates and their contents.
.. versionadded:: 20.8
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from typing import Optional
from telegram._utils.types import SCT
def parse_chat_id(chat_id: Optional[SCT[int]]) -> frozenset[int]:
"""Accepts a chat id or collection of chat ids and returns a frozenset of chat ids."""
if chat_id is None:
return frozenset()
if isinstance(chat_id, int):
return frozenset({chat_id})
return frozenset(chat_id)
def parse_username(username: Optional[SCT[str]]) -> frozenset[str]:
"""Accepts a username or collection of usernames and returns a frozenset of usernames.
Strips the leading ``@`` if present.
"""
if username is None:
return frozenset()
if isinstance(username, str):
return frozenset({username.removeprefix("@")})
return frozenset(usr.removeprefix("@") for usr in username)

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains helper functions related to the std-lib asyncio module.
.. versionadded:: 21.11
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import asyncio
from typing import Literal
class TrackedBoundedSemaphore(asyncio.BoundedSemaphore):
"""Simple subclass of :class:`asyncio.BoundedSemaphore` that tracks the current value of the
semaphore. While there is an attribute ``_value`` in the superclass, it's private and we
don't want to rely on it.
"""
__slots__ = ("_current_value",)
def __init__(self, value: int = 1) -> None:
super().__init__(value)
self._current_value = value
@property
def current_value(self) -> int:
return self._current_value
async def acquire(self) -> Literal[True]:
await super().acquire()
self._current_value -= 1
return True
def release(self) -> None:
super().release()
self._current_value += 1

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains a network retry loop implementation.
Its specifically tailored to handling the Telegram API and its errors.
.. versionadded:: 21.11
Hint:
It was originally part of the `Updater` class, but as part of #4657 it was extracted into its
own module to be used by other parts of the library.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import asyncio
import contextlib
from collections.abc import Coroutine
from typing import Callable, Optional
from telegram._utils.logging import get_logger
from telegram.error import InvalidToken, RetryAfter, TelegramError, TimedOut
_LOGGER = get_logger(__name__)
async def network_retry_loop(
*,
action_cb: Callable[..., Coroutine],
on_err_cb: Optional[Callable[[TelegramError], None]] = None,
description: str,
interval: float,
stop_event: Optional[asyncio.Event] = None,
is_running: Optional[Callable[[], bool]] = None,
max_retries: int,
) -> None:
"""Perform a loop calling `action_cb`, retrying after network errors.
Stop condition for loop:
* `is_running()` evaluates :obj:`False` or
* return value of `action_cb` evaluates :obj:`False`
* or `stop_event` is set.
* or `max_retries` is reached.
Args:
action_cb (:term:`coroutine function`): Network oriented callback function to call.
on_err_cb (:obj:`callable`): Optional. Callback to call when TelegramError is caught.
Receives the exception object as a parameter.
Hint:
Only required if you want to handle the error in a special way. Logging about
the error is already handled by the loop.
Important:
Must not raise exceptions! If it does, the loop will be aborted.
description (:obj:`str`): Description text to use for logs and exception raised.
interval (:obj:`float` | :obj:`int`): Interval to sleep between each call to
`action_cb`.
stop_event (:class:`asyncio.Event` | :obj:`None`): Event to wait on for stopping the
loop. Setting the event will make the loop exit even if `action_cb` is currently
running. Defaults to :obj:`None`.
is_running (:obj:`callable`): Function to check if the loop should continue running.
Must return a boolean value. Defaults to `lambda: True`.
max_retries (:obj:`int`): Maximum number of retries before stopping the loop.
* < 0: Retry indefinitely.
* 0: No retries.
* > 0: Number of retries.
"""
log_prefix = f"Network Retry Loop ({description}):"
effective_is_running = is_running or (lambda: True)
async def do_action() -> bool:
if not stop_event:
return await action_cb()
action_cb_task = asyncio.create_task(action_cb())
stop_task = asyncio.create_task(stop_event.wait())
done, pending = await asyncio.wait(
(action_cb_task, stop_task), return_when=asyncio.FIRST_COMPLETED
)
with contextlib.suppress(asyncio.CancelledError):
for task in pending:
task.cancel()
if stop_task in done:
_LOGGER.debug("%s Cancelled", log_prefix)
return False
return action_cb_task.result()
_LOGGER.debug("%s Starting", log_prefix)
cur_interval = interval
retries = 0
while effective_is_running():
try:
if not await do_action():
break
except RetryAfter as exc:
slack_time = 0.5
_LOGGER.info(
"%s %s. Adding %s seconds to the specified time.", log_prefix, exc, slack_time
)
# pylint: disable=protected-access
cur_interval = slack_time + exc._retry_after.total_seconds()
except TimedOut as toe:
_LOGGER.debug("%s Timed out: %s. Retrying immediately.", log_prefix, toe)
# If failure is due to timeout, we should retry asap.
cur_interval = 0
except InvalidToken:
_LOGGER.exception("%s Invalid token. Aborting retry loop.", log_prefix)
raise
except TelegramError as telegram_exc:
if on_err_cb:
on_err_cb(telegram_exc)
if max_retries < 0 or retries < max_retries:
_LOGGER.debug(
"%s Failed run number %s of %s. Retrying.", log_prefix, retries, max_retries
)
else:
_LOGGER.exception(
"%s Failed run number %s of %s. Aborting.", log_prefix, retries, max_retries
)
raise
# increase waiting times on subsequent errors up to 30secs
cur_interval = 1 if cur_interval == 0 else min(30, 1.5 * cur_interval)
else:
cur_interval = interval
finally:
retries += 1
if cur_interval:
await asyncio.sleep(cur_interval)

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains helper functions related to inspecting the program stack.
.. versionadded:: 20.0
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from pathlib import Path
from types import FrameType
from typing import Optional
from telegram._utils.logging import get_logger
_LOGGER = get_logger(__name__)
def was_called_by(frame: Optional[FrameType], caller: Path) -> bool:
"""Checks if the passed frame was called by the specified file.
Example:
.. code:: pycon
>>> was_called_by(inspect.currentframe(), Path(__file__))
True
Arguments:
frame (:obj:`FrameType`): The frame - usually the return value of
``inspect.currentframe()``. If :obj:`None` is passed, the return value will be
:obj:`False`.
caller (:obj:`pathlib.Path`): File that should be the caller.
Returns:
:obj:`bool`: Whether the frame was called by the specified file.
"""
if frame is None:
return False
try:
return _was_called_by(frame, caller)
except Exception as exc:
_LOGGER.debug(
"Failed to check if frame was called by `caller`. Assuming that it was not.",
exc_info=exc,
)
return False
def _was_called_by(frame: FrameType, caller: Path) -> bool:
# https://stackoverflow.com/a/57712700/10606962
if Path(frame.f_code.co_filename).resolve() == caller:
return True
while frame.f_back:
frame = frame.f_back
if Path(frame.f_code.co_filename).resolve() == caller:
return True
return False

View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains a mutable mapping that keeps track of the keys that where accessed.
.. versionadded:: 20.0
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections import UserDict
from collections.abc import Mapping
from typing import Final, Generic, Optional, TypeVar, Union
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
_VT = TypeVar("_VT")
_KT = TypeVar("_KT")
_T = TypeVar("_T")
class TrackingDict(UserDict, Generic[_KT, _VT]):
"""Mutable mapping that keeps track of which keys where accessed with write access.
Read-access is not tracked.
Note:
* ``setdefault()`` and ``pop`` are considered writing only depending on whether the
key is present
* deleting values is considered writing
"""
DELETED: Final = object()
"""Special marker indicating that an entry was deleted."""
__slots__ = ("_write_access_keys",)
def __init__(self) -> None:
super().__init__()
self._write_access_keys: set[_KT] = set()
def __setitem__(self, key: _KT, value: _VT) -> None:
self.__track_write(key)
super().__setitem__(key, value)
def __delitem__(self, key: _KT) -> None:
self.__track_write(key)
super().__delitem__(key)
def __track_write(self, key: Union[_KT, set[_KT]]) -> None:
if isinstance(key, set):
self._write_access_keys |= key
else:
self._write_access_keys.add(key)
def pop_accessed_keys(self) -> set[_KT]:
"""Returns all keys that were write-accessed since the last time this method was called."""
out = self._write_access_keys
self._write_access_keys = set()
return out
def pop_accessed_write_items(self) -> list[tuple[_KT, _VT]]:
"""
Returns all keys & corresponding values as set of tuples that were write-accessed since
the last time this method was called. If a key was deleted, the value will be
:attr:`DELETED`.
"""
keys = self.pop_accessed_keys()
return [(key, self.get(key, self.DELETED)) for key in keys]
def mark_as_accessed(self, key: _KT) -> None:
"""Use this method have the key returned again in the next call to
:meth:`pop_accessed_write_items` or :meth:`pop_accessed_keys`
"""
self._write_access_keys.add(key)
# Override methods to track access
def update_no_track(self, mapping: Mapping[_KT, _VT]) -> None:
"""Like ``update``, but doesn't count towards write access."""
for key, value in mapping.items():
self.data[key] = value
# Mypy seems a bit inconsistent about what it wants as types for `default` and return value
# so we just ignore a bit
def pop( # type: ignore[override]
self,
key: _KT,
default: _VT = DEFAULT_NONE, # type: ignore[assignment]
) -> _VT:
if key in self:
self.__track_write(key)
if isinstance(default, DefaultValue):
return super().pop(key)
return super().pop(key, default=default)
def clear(self) -> None:
self.__track_write(set(super().keys()))
super().clear()
# Mypy seems a bit inconsistent about what it wants as types for `default` and return value
# so we just ignore a bit
def setdefault(self: "TrackingDict[_KT, _T]", key: _KT, default: Optional[_T] = None) -> _T:
if key in self:
return self[key]
self.__track_write(key)
self[key] = default # type: ignore[assignment]
return default # type: ignore[return-value]

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""This module contains custom typing aliases.
.. versionadded:: 13.6
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections.abc import Coroutine, MutableMapping
from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
if TYPE_CHECKING:
from typing import Optional
from telegram import Bot
from telegram.ext import BaseRateLimiter, CallbackContext, JobQueue
CCT = TypeVar("CCT", bound="CallbackContext[Any, Any, Any, Any]")
"""An instance of :class:`telegram.ext.CallbackContext` or a custom subclass.
.. versionadded:: 13.6
"""
RT = TypeVar("RT")
UT = TypeVar("UT")
HandlerCallback = Callable[[UT, CCT], Coroutine[Any, Any, RT]]
"""Type of a handler callback
.. versionadded:: 20.0
"""
JobCallback = Callable[[CCT], Coroutine[Any, Any, Any]]
"""Type of a job callback
.. versionadded:: 20.0
"""
ConversationKey = tuple[Union[int, str], ...]
ConversationDict = MutableMapping[ConversationKey, object]
"""dict[tuple[:obj:`int` | :obj:`str`, ...], Optional[:obj:`object`]]:
Dicts as maintained by the :class:`telegram.ext.ConversationHandler`.
.. versionadded:: 13.6
"""
CDCData = tuple[list[tuple[str, float, dict[str, Any]]], dict[str, str]]
"""tuple[list[tuple[:obj:`str`, :obj:`float`, dict[:obj:`str`, :class:`object`]]], \
dict[:obj:`str`, :obj:`str`]]: Data returned by
:attr:`telegram.ext.CallbackDataCache.persistence_data`.
.. versionadded:: 13.6
"""
BT = TypeVar("BT", bound="Bot")
"""Type of the bot.
.. versionadded:: 20.0
"""
UD = TypeVar("UD")
"""Type of the user data for a single user.
.. versionadded:: 13.6
"""
CD = TypeVar("CD")
"""Type of the chat data for a single user.
.. versionadded:: 13.6
"""
BD = TypeVar("BD")
"""Type of the bot data.
.. versionadded:: 13.6
"""
JQ = TypeVar("JQ", bound=Union[None, "JobQueue"])
"""Type of the job queue.
.. versionadded:: 20.0"""
RL = TypeVar("RL", bound="Optional[BaseRateLimiter]")
"""Type of the rate limiter.
.. versionadded:: 20.0"""
RLARGS = TypeVar("RLARGS")
"""Type of the rate limiter arguments.
.. versionadded:: 20.0"""
FilterDataDict = dict[str, list[Any]]

View File

@@ -0,0 +1,223 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
# pylint: disable=missing-module-docstring
import asyncio
import json
from http import HTTPStatus
from pathlib import Path
from socket import socket
from ssl import SSLContext
from types import TracebackType
from typing import TYPE_CHECKING, Optional, Union
# Instead of checking for ImportError here, we do that in `updater.py`, where we import from
# this module. Doing it here would be tricky, as the classes below subclass tornado classes
import tornado.web
from tornado.httpserver import HTTPServer
try:
from tornado.netutil import bind_unix_socket
UNIX_AVAILABLE = True
except ImportError:
UNIX_AVAILABLE = False
from telegram import Update
from telegram._utils.logging import get_logger
from telegram.ext._extbot import ExtBot
if TYPE_CHECKING:
from telegram import Bot
# This module is not visible to users, so we log as Updater
_LOGGER = get_logger(__name__, class_name="Updater")
class WebhookServer:
"""Thin wrapper around ``tornado.httpserver.HTTPServer``."""
__slots__ = (
"_http_server",
"_server_lock",
"_shutdown_lock",
"is_running",
"listen",
"port",
"unix",
)
def __init__(
self,
listen: str,
port: int,
webhook_app: "WebhookAppClass",
ssl_ctx: Optional[SSLContext],
unix: Optional[Union[str, Path, socket]] = None,
):
if unix and not UNIX_AVAILABLE:
raise RuntimeError("This OS does not support binding unix sockets.")
self._http_server = HTTPServer(webhook_app, ssl_options=ssl_ctx)
self.listen = listen
self.port = port
self.is_running = False
self.unix = None
if unix and isinstance(unix, socket):
self.unix = unix
elif unix:
self.unix = bind_unix_socket(str(unix))
self._server_lock = asyncio.Lock()
self._shutdown_lock = asyncio.Lock()
async def serve_forever(self, ready: Optional[asyncio.Event] = None) -> None:
async with self._server_lock:
if self.unix:
self._http_server.add_socket(self.unix)
else:
self._http_server.listen(self.port, address=self.listen)
self.is_running = True
if ready is not None:
ready.set()
_LOGGER.debug("Webhook Server started.")
async def shutdown(self) -> None:
async with self._shutdown_lock:
if not self.is_running:
_LOGGER.debug("Webhook Server is already shut down. Returning")
return
self.is_running = False
self._http_server.stop()
await self._http_server.close_all_connections()
_LOGGER.debug("Webhook Server stopped")
class WebhookAppClass(tornado.web.Application):
"""Application used in the Webserver"""
def __init__(
self,
webhook_path: str,
bot: "Bot",
update_queue: asyncio.Queue,
secret_token: Optional[str] = None,
):
self.shared_objects = {
"bot": bot,
"update_queue": update_queue,
"secret_token": secret_token,
}
handlers = [(rf"{webhook_path}/?", TelegramHandler, self.shared_objects)]
tornado.web.Application.__init__(self, handlers) # type: ignore
def log_request(self, handler: tornado.web.RequestHandler) -> None:
"""Overrides the default implementation since we have our own logging setup."""
# pylint: disable=abstract-method
class TelegramHandler(tornado.web.RequestHandler):
"""BaseHandler that processes incoming requests from Telegram"""
__slots__ = ("bot", "secret_token", "update_queue")
SUPPORTED_METHODS = ("POST",) # type: ignore[assignment]
def initialize(self, bot: "Bot", update_queue: asyncio.Queue, secret_token: str) -> None:
"""Initialize for each request - that's the interface provided by tornado"""
# pylint: disable=attribute-defined-outside-init
self.bot = bot
self.update_queue = update_queue
self.secret_token = secret_token
if secret_token:
_LOGGER.debug(
"The webhook server has a secret token, expecting it in incoming requests now"
)
def set_default_headers(self) -> None:
"""Sets default headers"""
self.set_header("Content-Type", 'application/json; charset="utf-8"')
async def post(self) -> None:
"""Handle incoming POST request"""
_LOGGER.debug("Webhook triggered")
self._validate_post()
json_string = self.request.body.decode()
data = json.loads(json_string)
self.set_status(HTTPStatus.OK)
_LOGGER.debug("Webhook received data: %s", json_string)
try:
update = Update.de_json(data, self.bot)
except Exception as exc:
_LOGGER.critical(
"Something went wrong processing the data received from Telegram. "
"Received data was *not* processed! Received data was: %r",
data,
exc_info=exc,
)
raise tornado.web.HTTPError(
HTTPStatus.BAD_REQUEST, reason="Update could not be processed"
) from exc
if update:
_LOGGER.debug(
"Received Update with ID %d on Webhook",
# For some reason pylint thinks update is a general TelegramObject
update.update_id, # pylint: disable=no-member
)
# handle arbitrary callback data, if necessary
if isinstance(self.bot, ExtBot):
self.bot.insert_callback_data(update)
await self.update_queue.put(update)
def _validate_post(self) -> None:
"""Only accept requests with content type JSON"""
ct_header = self.request.headers.get("Content-Type", None)
if ct_header != "application/json":
raise tornado.web.HTTPError(HTTPStatus.FORBIDDEN)
# verifying that the secret token is the one the user set when the user set one
if self.secret_token is not None:
token = self.request.headers.get("X-Telegram-Bot-Api-Secret-Token")
if not token:
_LOGGER.debug("Request did not include the secret token")
raise tornado.web.HTTPError(
HTTPStatus.FORBIDDEN, reason="Request did not include the secret token"
)
if token != self.secret_token:
_LOGGER.debug("Request had the wrong secret token: %s", token)
raise tornado.web.HTTPError(
HTTPStatus.FORBIDDEN, reason="Request had the wrong secret token"
)
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."""
_LOGGER.debug(
"%s - %s",
self.request.remote_ip,
"Exception in TelegramHandler",
exc_info=(typ, value, tb) if typ and value and tb else value,
)