diff --git a/telegram/bot.py b/telegram/bot.py index daad48e5722..46c142ed9b5 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -2521,6 +2521,8 @@ def set_webhook( max_connections: int = 40, allowed_updates: List[str] = None, api_kwargs: JSONDict = None, + ip_address: str = None, + drop_pending_updates: bool = None, ) -> bool: """ Use this method to specify a url and receive incoming updates via an outgoing webhook. @@ -2541,6 +2543,8 @@ def set_webhook( certificate (:obj:`filelike`): Upload your public key certificate so that the root certificate in use can be checked. See our self-signed guide for details. (https://goo.gl/rw7w6Y) + ip_address (:obj:`str`, optional): The fixed IP address which will be used to send + webhook requests instead of the IP address resolved through DNS. max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, and higher values to increase your @@ -2553,6 +2557,8 @@ def set_webhook( specified, the previous setting will be used. Please note that this parameter doesn't affect updates created before the call to the set_webhook, so unwanted updates may be received for a short period of time. + drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending + updates. timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the read timeout from the server (instead of the one specified during creation of the connection pool). @@ -2592,18 +2598,26 @@ def set_webhook( data['max_connections'] = max_connections if allowed_updates is not None: data['allowed_updates'] = allowed_updates + if ip_address: + data['ip_address'] = ip_address + if drop_pending_updates: + data['drop_pending_updates'] = drop_pending_updates result = self._post('setWebhook', data, timeout=timeout, api_kwargs=api_kwargs) return result # type: ignore[return-value] @log - def delete_webhook(self, timeout: float = None, api_kwargs: JSONDict = None) -> bool: + def delete_webhook( + self, timeout: float = None, api_kwargs: JSONDict = None, drop_pending_updates: bool = None + ) -> bool: """ Use this method to remove webhook integration if you decide to switch back to getUpdates. Requires no parameters. Args: + drop_pending_updates(:obj:`bool`, optional): Pass :obj:`True`: to drop all pending + updates. timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the read timeout from the server (instead of the one specified during creation of the connection pool). @@ -2617,7 +2631,12 @@ def delete_webhook(self, timeout: float = None, api_kwargs: JSONDict = None) -> :class:`telegram.TelegramError` """ - result = self._post('deleteWebhook', None, timeout=timeout, api_kwargs=api_kwargs) + data = {} + + if drop_pending_updates: + data['drop_pending_updates'] = drop_pending_updates + + result = self._post('deleteWebhook', data, timeout=timeout, api_kwargs=api_kwargs) return result # type: ignore[return-value] @@ -4360,6 +4379,41 @@ def set_my_commands( return result # type: ignore[return-value] + @log + def log_out(self) -> bool: + """ + Use this method to log out from the cloud Bot API server before launching the bot locally. + You *must* log out the bot before running it locally, otherwise there is no guarantee that + the bot will receive updates. After a successful call, you can immediately log in on a + local server, but will not be able to log in back to the cloud Bot API server for 10 + minutes. + + Returns: + :obj:`True`: On success + + Raises: + :class:`telegram.TelegramError` + + """ + return self._post('logOut') # type: ignore[return-value] + + @log + def close(self) -> bool: + """ + Use this method to close the bot instance before moving it from one local server to + another. You need to delete the webhook before calling this method to ensure that the bot + isn't launched again after server restart. The method will return error 429 in the first + 10 minutes after the bot is launched. + + Returns: + :obj:`True`: On success + + Raises: + :class:`telegram.TelegramError` + + """ + return self._post('close') # type: ignore[return-value] + def to_dict(self) -> JSONDict: data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name} @@ -4509,3 +4563,5 @@ def to_dict(self) -> JSONDict: """Alias for :attr:`get_my_commands`""" setMyCommands = set_my_commands """Alias for :attr:`set_my_commands`""" + logOut = log_out + """Alias for :attr:`log_out`""" diff --git a/telegram/webhookinfo.py b/telegram/webhookinfo.py index a34736544e8..dcfa6ae7571 100644 --- a/telegram/webhookinfo.py +++ b/telegram/webhookinfo.py @@ -30,13 +30,14 @@ class WebhookInfo(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`url`, :attr:`has_custom_certificate`, - :attr:`pending_update_count`, :attr:`last_error_date`, :attr:`last_error_message`, - :attr:`max_connections` and :attr:`allowed_updates` are equal. + :attr:`pending_update_count`, :attr:`ip_address`, :attr:`last_error_date`, + :attr:`last_error_message`, :attr:`max_connections` and :attr:`allowed_updates` are equal. Attributes: url (:obj:`str`): Webhook URL. has_custom_certificate (:obj:`bool`): If a custom certificate was provided for webhook. pending_update_count (:obj:`int`): Number of updates awaiting delivery. + ip_address (:obj:`str`): Optional. Currently used webhook IP address. last_error_date (:obj:`int`): Optional. Unix time for the most recent error that happened. last_error_message (:obj:`str`): Optional. Error message in human-readable format. max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS @@ -49,6 +50,7 @@ class WebhookInfo(TelegramObject): has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for webhook certificate checks. pending_update_count (:obj:`int`): Number of updates awaiting delivery. + ip_address (:obj:`str`, optional): Currently used webhook IP address. last_error_date (:obj:`int`, optional): Unix time for the most recent error that happened when trying to deliver an update via webhook. last_error_message (:obj:`str`, optional): Error message in human-readable format for the @@ -69,12 +71,16 @@ def __init__( last_error_message: str = None, max_connections: int = None, allowed_updates: List[str] = None, + ip_address: str = None, **_kwargs: Any, ): # Required self.url = url self.has_custom_certificate = has_custom_certificate self.pending_update_count = pending_update_count + + # Optional + self.ip_address = ip_address self.last_error_date = last_error_date self.last_error_message = last_error_message self.max_connections = max_connections @@ -84,6 +90,7 @@ def __init__( self.url, self.has_custom_certificate, self.pending_update_count, + self.ip_address, self.last_error_date, self.last_error_message, self.max_connections, diff --git a/tests/test_bot.py b/tests/test_bot.py index f12e51671fe..75530e991ec 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -922,7 +922,12 @@ def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot): url = 'https://python-telegram-bot.org/test/webhook' max_connections = 7 allowed_updates = ['message'] - bot.set_webhook(url, max_connections=max_connections, allowed_updates=allowed_updates) + bot.set_webhook( + url, + max_connections=max_connections, + allowed_updates=allowed_updates, + ip_address='127.0.0.1', + ) time.sleep(2) live_info = bot.get_webhook_info() time.sleep(6) @@ -933,6 +938,19 @@ def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot): assert live_info.url == url assert live_info.max_connections == max_connections assert live_info.allowed_updates == allowed_updates + assert live_info.ip_address == '127.0.0.1' + + @pytest.mark.parametrize('drop_pending_updates', [True, False]) + def test_set_webhook_delete_webhook_drop_pending_updates( + self, bot, drop_pending_updates, monkeypatch + ): + def assertion(url, data, *args, **kwargs): + return bool(data.get('drop_pending_updates')) == drop_pending_updates + + monkeypatch.setattr(bot.request, 'post', assertion) + + assert bot.set_webhook(drop_pending_updates=drop_pending_updates) + assert bot.delete_webhook(drop_pending_updates=drop_pending_updates) @flaky(3, 1) @pytest.mark.timeout(10) @@ -1379,3 +1397,21 @@ def test_set_and_get_my_commands_strings(self, bot): assert bc[0].description == 'descr1' assert bc[1].command == 'cmd2' assert bc[1].description == 'descr2' + + def test_log_out(self, monkeypatch, bot): + # We don't actually make a request as to not break the test setup + def assertion(url, data, *args, **kwargs): + return data == {} and url.split('/')[-1] == 'logOut' + + monkeypatch.setattr(bot.request, 'post', assertion) + + assert bot.log_out() + + def test_close(self, monkeypatch, bot): + # We don't actually make a request as to not break the test setup + def assertion(url, data, *args, **kwargs): + return data == {} and url.split('/')[-1] == 'close' + + monkeypatch.setattr(bot.request, 'post', assertion) + + assert bot.close() diff --git a/tests/test_webhookinfo.py b/tests/test_webhookinfo.py index 6d27277353f..be200c6fed7 100644 --- a/tests/test_webhookinfo.py +++ b/tests/test_webhookinfo.py @@ -29,6 +29,7 @@ def webhook_info(): url=TestWebhookInfo.url, has_custom_certificate=TestWebhookInfo.has_custom_certificate, pending_update_count=TestWebhookInfo.pending_update_count, + ip_address=TestWebhookInfo.ip_address, last_error_date=TestWebhookInfo.last_error_date, max_connections=TestWebhookInfo.max_connections, allowed_updates=TestWebhookInfo.allowed_updates, @@ -39,6 +40,7 @@ class TestWebhookInfo(object): url = "http://www.google.com" has_custom_certificate = False pending_update_count = 5 + ip_address = '127.0.0.1' last_error_date = time.time() max_connections = 42 allowed_updates = ['type1', 'type2'] @@ -52,6 +54,7 @@ def test_to_dict(self, webhook_info): assert webhook_info_dict['last_error_date'] == self.last_error_date assert webhook_info_dict['max_connections'] == self.max_connections assert webhook_info_dict['allowed_updates'] == self.allowed_updates + assert webhook_info_dict['ip_address'] == self.ip_address def test_equality(self): a = WebhookInfo(