96
96
from telegram ._utils .logging import get_logger
97
97
from telegram ._utils .repr import build_repr_with_selected_attrs
98
98
from telegram ._utils .strings import to_camel_case
99
- from telegram ._utils .types import CorrectOptionID , FileInput , JSONDict , ODVInput , ReplyMarkup
99
+ from telegram ._utils .types import (
100
+ BaseUrl ,
101
+ CorrectOptionID ,
102
+ FileInput ,
103
+ JSONDict ,
104
+ ODVInput ,
105
+ ReplyMarkup ,
106
+ )
100
107
from telegram ._utils .warnings import warn
101
108
from telegram ._webhookinfo import WebhookInfo
102
109
from telegram .constants import InlineQueryLimit , ReactionEmoji
126
133
BT = TypeVar ("BT" , bound = "Bot" )
127
134
128
135
136
+ # Even though we document only {token} as supported insertion, we are a bit more flexible
137
+ # internally and support additional variants. At the very least, we don't want the insertion
138
+ # to be case sensitive.
139
+ _SUPPORTED_INSERTIONS = {"token" , "TOKEN" , "bot_token" , "BOT_TOKEN" , "bot-token" , "BOT-TOKEN" }
140
+ _INSERTION_STRINGS = {f"{{{ insertion } }}" for insertion in _SUPPORTED_INSERTIONS }
141
+
142
+
143
+ class _TokenDict (dict ):
144
+ __slots__ = ("token" ,)
145
+
146
+ # small helper to make .format_map work without knowing which exact insertion name is used
147
+ def __init__ (self , token : str ):
148
+ self .token = token
149
+ super ().__init__ ()
150
+
151
+ def __missing__ (self , key : str ) -> str :
152
+ if key in _SUPPORTED_INSERTIONS :
153
+ return self .token
154
+ raise KeyError (f"Base URL string contains unsupported insertion: { key } " )
155
+
156
+
157
+ def _parse_base_url (value : BaseUrl , token : str ) -> str :
158
+ if callable (value ):
159
+ return value (token )
160
+ if any (insertion in value for insertion in _INSERTION_STRINGS ):
161
+ return value .format_map (_TokenDict (token ))
162
+ return value + token
163
+
164
+
129
165
class Bot (TelegramObject , contextlib .AbstractAsyncContextManager ["Bot" ]):
130
166
"""This object represents a Telegram Bot.
131
167
@@ -193,8 +229,40 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
193
229
194
230
Args:
195
231
token (:obj:`str`): Bot's unique authentication token.
196
- base_url (:obj:`str`, optional): Telegram Bot API service URL.
232
+ base_url (:obj:`str` | Callable[[:obj:`str`], :obj:`str`], optional): Telegram Bot API
233
+ service URL. If the string contains ``{token}``, it will be replaced with the bot's
234
+ token. If a callable is passed, it will be called with the bot's token as the only
235
+ argument and must return the base URL. Otherwise, the token will be appended to the
236
+ string. Defaults to ``"https://api.telegram.org/bot"``.
237
+
238
+ Tip:
239
+ Customizing the base URL can be used to run a bot against
240
+ :wiki:`Local Bot API Server <Local-Bot-API-Server>` or using Telegrams
241
+ `test environment \
242
+ <https://core.telegram.org/bots/features#dedicated-test-environment>`_.
243
+
244
+ Example:
245
+ ``"https://api.telegram.org/bot{token}/test"``
246
+
247
+ .. versionchanged:: NEXT.VERSION
248
+ Supports callable input and string formatting.
197
249
base_file_url (:obj:`str`, optional): Telegram Bot API file URL.
250
+ If the string contains ``{token}``, it will be replaced with the bot's
251
+ token. If a callable is passed, it will be called with the bot's token as the only
252
+ argument and must return the base URL. Otherwise, the token will be appended to the
253
+ string. Defaults to ``"https://api.telegram.org/bot"``.
254
+
255
+ Tip:
256
+ Customizing the base URL can be used to run a bot against
257
+ :wiki:`Local Bot API Server <Local-Bot-API-Server>` or using Telegrams
258
+ `test environment \
259
+ <https://core.telegram.org/bots/features#dedicated-test-environment>`_.
260
+
261
+ Example:
262
+ ``"https://api.telegram.org/file/bot{token}/test"``
263
+
264
+ .. versionchanged:: NEXT.VERSION
265
+ Supports callable input and string formatting.
198
266
request (:class:`telegram.request.BaseRequest`, optional): Pre initialized
199
267
:class:`telegram.request.BaseRequest` instances. Will be used for all bot methods
200
268
*except* for :meth:`get_updates`. If not passed, an instance of
@@ -239,8 +307,8 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
239
307
def __init__ (
240
308
self ,
241
309
token : str ,
242
- base_url : str = "https://api.telegram.org/bot" ,
243
- base_file_url : str = "https://api.telegram.org/file/bot" ,
310
+ base_url : BaseUrl = "https://api.telegram.org/bot" ,
311
+ base_file_url : BaseUrl = "https://api.telegram.org/file/bot" ,
244
312
request : Optional [BaseRequest ] = None ,
245
313
get_updates_request : Optional [BaseRequest ] = None ,
246
314
private_key : Optional [bytes ] = None ,
@@ -252,8 +320,11 @@ def __init__(
252
320
raise InvalidToken ("You must pass the token you received from https://t.me/Botfather!" )
253
321
self ._token : str = token
254
322
255
- self ._base_url : str = base_url + self ._token
256
- self ._base_file_url : str = base_file_url + self ._token
323
+ self ._base_url : str = _parse_base_url (base_url , self ._token )
324
+ self ._base_file_url : str = _parse_base_url (base_file_url , self ._token )
325
+ self ._LOGGER .debug ("Set Bot API URL: %s" , self ._base_url )
326
+ self ._LOGGER .debug ("Set Bot API File URL: %s" , self ._base_file_url )
327
+
257
328
self ._local_mode : bool = local_mode
258
329
self ._bot_user : Optional [User ] = None
259
330
self ._private_key : Optional [bytes ] = None
@@ -264,7 +335,7 @@ def __init__(
264
335
HTTPXRequest () if request is None else request ,
265
336
)
266
337
267
- # this section is about issuing a warning when using HTTP/2 and connect to a self hosted
338
+ # this section is about issuing a warning when using HTTP/2 and connect to a self- hosted
268
339
# bot api instance, which currently only supports HTTP/1.1. Checking if a custom base url
269
340
# is set is the best way to do that.
270
341
@@ -273,14 +344,14 @@ def __init__(
273
344
if (
274
345
isinstance (self ._request [0 ], HTTPXRequest )
275
346
and self ._request [0 ].http_version == "2"
276
- and not base_url .startswith ("https://api.telegram.org/bot" )
347
+ and not self . base_url .startswith ("https://api.telegram.org/bot" )
277
348
):
278
349
warning_string = "get_updates_request"
279
350
280
351
if (
281
352
isinstance (self ._request [1 ], HTTPXRequest )
282
353
and self ._request [1 ].http_version == "2"
283
- and not base_url .startswith ("https://api.telegram.org/bot" )
354
+ and not self . base_url .startswith ("https://api.telegram.org/bot" )
284
355
):
285
356
if warning_string :
286
357
warning_string += " and request"
0 commit comments