From a7cafd1f2448aa1c1761ae44454cb09529f68d3b Mon Sep 17 00:00:00 2001 From: _run Date: Fri, 21 Apr 2023 21:27:08 +0400 Subject: [PATCH 1/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f98be8..aa8b32e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

A simple, but extensible Python implementation for the Telegram Bot API.

Both synchronous and asynchronous.

-##

Supported Bot API version: 6.6! +##

Supported Bot API version: 6.7!

Official documentation

Official ru documentation

From d1417e561676bbef7ebe536747a669e4aacc93c6 Mon Sep 17 00:00:00 2001 From: coder2020official Date: Fri, 21 Apr 2023 22:14:16 +0400 Subject: [PATCH 2/9] Added the field web_app_name to the class WriteAccessAllowed. --- telebot/types.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 22d46c9..fed442c 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -7396,13 +7396,20 @@ class WriteAccessAllowed(JsonDeserializable): Currently holds no information. Telegram documentation: https://core.telegram.org/bots/api#writeaccessallowed + + :param web_app_name: Optional. Name of the Web App which was launched from a link + :type web_app_name: :obj:`str` """ @classmethod def de_json(cls, json_string): - return cls() + if json_string is None: return None + obj = cls.check_json(json_string) + return cls(**obj) + - def __init__(self) -> None: - pass + def __init__(self, web_app_name: str) -> None: + self.web_app_name: str = web_app_name + class UserShared(JsonDeserializable): From 966b45186906805a012a58c32600e077629b4589 Mon Sep 17 00:00:00 2001 From: coder2020official Date: Fri, 21 Apr 2023 22:28:20 +0400 Subject: [PATCH 3/9] Added the field switch_inline_query_chosen_chat of the type SwitchInlineQueryChosenChat to the class InlineKeyboardButton, which allows bots to switch to inline mode in a chosen chat of the given type. --- telebot/types.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index fed442c..f23d7fc 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2592,6 +2592,10 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable) something from multiple options. :type switch_inline_query_current_chat: :obj:`str` + :param switch_inline_query_chosen_chat: Optional. If set, pressing the button will prompt the user to select one of their chats of the + specified type, open that chat and insert the bot's username and the specified inline query in the input field + :type switch_inline_query_chosen_chat: :class:`telebot.types.SwitchInlineQueryChosenChat` + :param callback_game: Optional. Description of the game that will be launched when the user presses the button. NOTE: This type of button must always be the first button in the first row. :type callback_game: :class:`telebot.types.CallbackGame` @@ -2611,17 +2615,20 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable) obj['login_url'] = LoginUrl.de_json(obj.get('login_url')) if 'web_app' in obj: obj['web_app'] = WebAppInfo.de_json(obj.get('web_app')) + if 'switch_inline_query_chosen_chat' in obj: + obj['switch_inline_query_chosen_chat'] = SwitchInlineQueryChosenChat.de_json(obj.get('switch_inline_query_chosen_chat')) return cls(**obj) def __init__(self, text, url=None, callback_data=None, web_app=None, switch_inline_query=None, - switch_inline_query_current_chat=None, callback_game=None, pay=None, login_url=None, **kwargs): + switch_inline_query_current_chat=None, switch_inline_query_chosen_chat=None, callback_game=None, pay=None, login_url=None, **kwargs): self.text: str = text self.url: str = url self.callback_data: str = callback_data self.web_app: WebAppInfo = web_app self.switch_inline_query: str = switch_inline_query self.switch_inline_query_current_chat: str = switch_inline_query_current_chat + self.switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat = switch_inline_query_chosen_chat self.callback_game = callback_game # Not Implemented self.pay: bool = pay self.login_url: LoginUrl = login_url @@ -2647,6 +2654,8 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable) json_dict['pay'] = self.pay if self.login_url is not None: json_dict['login_url'] = self.login_url.to_dict() + if self.switch_inline_query_chosen_chat is not None: + json_dict['switch_inline_query_chosen_chat'] = self.switch_inline_query_chosen_chat.to_dict() return json_dict @@ -7583,4 +7592,64 @@ class InputSticker(Dictionaryable, JsonSerializable): return self.to_json(), {self._sticker_name: self.sticker} - \ No newline at end of file + +class SwitchInlineQueryChosenChat(JsonDeserializable, Dictionaryable, JsonSerializable): + """ + Represents an inline button that switches the current user to inline mode in a chosen chat, + with an optional default inline query. + + Telegram Documentation: https://core.telegram.org/bots/api#inlinekeyboardbutton + + :param query: Optional. The default inline query to be inserted in the input field. + If left empty, only the bot's username will be inserted + :type query: :obj:`str` + + :param allow_user_chats: Optional. True, if private chats with users can be chosen + :type allow_user_chats: :obj:`bool` + + :param allow_bot_chats: Optional. True, if private chats with bots can be chosen + :type allow_bot_chats: :obj:`bool` + + :param allow_group_chats: Optional. True, if group and supergroup chats can be chosen + :type allow_group_chats: :obj:`bool` + + :param allow_channel_chats: Optional. True, if channel chats can be chosen + :type allow_channel_chats: :obj:`bool` + + :return: Instance of the class + :rtype: :class:`SwitchInlineQueryChosenChat` + """ + + @classmethod + def de_json(cls, json_string): + if json_string is None: + return None + obj = cls.check_json(json_string) + return cls(**obj) + + def __init__(self, query=None, allow_user_chats=None, allow_bot_chats=None, allow_group_chats=None, + allow_channel_chats=None): + self.query: str = query + self.allow_user_chats: bool = allow_user_chats + self.allow_bot_chats: bool = allow_bot_chats + self.allow_group_chats: bool = allow_group_chats + self.allow_channel_chats: bool = allow_channel_chats + + def to_dict(self): + json_dict = {} + + if self.query is not None: + json_dict['query'] = self.query + if self.allow_user_chats is not None: + json_dict['allow_user_chats'] = self.allow_user_chats + if self.allow_bot_chats is not None: + json_dict['allow_bot_chats'] = self.allow_bot_chats + if self.allow_group_chats is not None: + json_dict['allow_group_chats'] = self.allow_group_chats + if self.allow_channel_chats is not None: + json_dict['allow_channel_chats'] = self.allow_channel_chats + + return json_dict + + def to_json(self): + return json.dumps(self.to_dict()) From d6f4987197d9450d10a4c823f72cb67bbd832cc8 Mon Sep 17 00:00:00 2001 From: coder2020official Date: Fri, 21 Apr 2023 22:30:32 +0400 Subject: [PATCH 4/9] Added the field via_chat_folder_invite_link to the class ChatMemberUpdated. --- telebot/types.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/telebot/types.py b/telebot/types.py index f23d7fc..53b692d 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -237,6 +237,9 @@ class ChatMemberUpdated(JsonDeserializable): link events only. :type invite_link: :class:`telebot.types.ChatInviteLink` + :param via_chat_folder_invite_link: Optional. True, if the user joined the chat via a chat folder invite link + :type via_chat_folder_invite_link: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberUpdated` """ @@ -251,13 +254,15 @@ class ChatMemberUpdated(JsonDeserializable): obj['invite_link'] = ChatInviteLink.de_json(obj.get('invite_link')) return cls(**obj) - def __init__(self, chat, from_user, date, old_chat_member, new_chat_member, invite_link=None, **kwargs): + def __init__(self, chat, from_user, date, old_chat_member, new_chat_member, invite_link=None, via_chat_folder_invite_link=None, + **kwargs): self.chat: Chat = chat self.from_user: User = from_user self.date: int = date self.old_chat_member: ChatMember = old_chat_member self.new_chat_member: ChatMember = new_chat_member self.invite_link: Optional[ChatInviteLink] = invite_link + self.via_chat_folder_invite_link: Optional[bool] = via_chat_folder_invite_link @property def difference(self) -> Dict[str, List]: From 77e19286280c312be6802583d66d03e14d3ee35c Mon Sep 17 00:00:00 2001 From: coder2020official Date: Sat, 22 Apr 2023 18:25:24 +0400 Subject: [PATCH 5/9] Added the ability to set different bot names for different user languages using the method setMyName. --- telebot/__init__.py | 26 ++++++++++++++++++++++++++ telebot/apihelper.py | 9 +++++++++ telebot/async_telebot.py | 18 ++++++++++++++++++ telebot/asyncio_helper.py | 10 ++++++++++ 4 files changed, 63 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index c385a29..549a69d 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -3443,6 +3443,24 @@ class TeleBot: """ result = apihelper.get_my_commands(self.token, scope, language_code) return [types.BotCommand.de_json(cmd) for cmd in result] + + def set_my_name(self, name: Optional[str]=None, language_code: Optional[str]=None): + """ + Use this method to change the bot's name. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#setmyname + + :param name: Optional. New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language. + :type name: :obj:`str` + + :param language_code: Optional. A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose + language there is no dedicated name. + :type language_code: :obj:`str` + + :return: True on success. + """ + + return apihelper.set_my_name(self.token, name, language_code) def set_my_description(self, description: Optional[str]=None, language_code: Optional[str]=None): """ @@ -3450,6 +3468,8 @@ class TeleBot: the chat with the bot if the chat is empty. Returns True on success. + Telegram documentation: https://core.telegram.org/bots/api#setmydescription + :param description: New bot description; 0-512 characters. Pass an empty string to remove the dedicated description for the given language. :type description: :obj:`str` @@ -3467,6 +3487,8 @@ class TeleBot: Use this method to get the current bot description for the given user language. Returns BotDescription on success. + Telegram documentation: https://core.telegram.org/bots/api#getmydescription + :param language_code: A two-letter ISO 639-1 language code or an empty string :type language_code: :obj:`str` @@ -3481,6 +3503,8 @@ class TeleBot: is sent together with the link when users share the bot. Returns True on success. + Telegram documentation: https://core.telegram.org/bots/api#setmyshortdescription + :param short_description: New short description for the bot; 0-120 characters. Pass an empty string to remove the dedicated short description for the given language. :type short_description: :obj:`str` @@ -3498,6 +3522,8 @@ class TeleBot: Use this method to get the current bot short description for the given user language. Returns BotShortDescription on success. + Telegram documentation: https://core.telegram.org/bots/api#getmyshortdescription + :param language_code: A two-letter ISO 639-1 language code or an empty string :type language_code: :obj:`str` diff --git a/telebot/apihelper.py b/telebot/apihelper.py index b5eb065..8d2b1e7 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1196,6 +1196,15 @@ def get_my_commands(token, scope=None, language_code=None): payload['language_code'] = language_code return _make_request(token, method_url, params=payload) +def set_my_name(token, name=None, language_code=None): + method_url = r'setMyName' + payload = {} + if name is not None: + payload['name'] = name + if language_code is not None: + payload['language_code'] = language_code + return _make_request(token, method_url, params=payload, method='post') + def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index ebf5e80..242e74c 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -4361,6 +4361,24 @@ class AsyncTeleBot: result = await asyncio_helper.get_my_commands(self.token, scope, language_code) return [types.BotCommand.de_json(cmd) for cmd in result] + async def set_my_name(self, name: Optional[str]=None, language_code: Optional[str]=None): + """ + Use this method to change the bot's name. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#setmyname + + :param name: Optional. New bot name; 0-64 characters. Pass an empty string to remove the dedicated name for the given language. + :type name: :obj:`str` + + :param language_code: Optional. A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose + language there is no dedicated name. + :type language_code: :obj:`str` + + :return: True on success. + """ + + return await asyncio_helper.set_my_name(self.token, name, language_code) + async def set_chat_menu_button(self, chat_id: Union[int, str]=None, menu_button: types.MenuButton=None) -> bool: """ diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 6f7ef0f..804e516 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1183,6 +1183,16 @@ async def get_my_commands(token, scope=None, language_code=None): payload['language_code'] = language_code return await _process_request(token, method_url, params=payload) + +async def set_my_name(token, name=None, language_code=None): + method_url = r'setMyName' + payload = {} + if name is not None: + payload['name'] = name + if language_code is not None: + payload['language_code'] = language_code + return await _process_request(token, method_url, params=payload, method='post') + async def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} From 1d62adc26261da555c2c2b6e7cf9b588c4bec3d2 Mon Sep 17 00:00:00 2001 From: coder2020official Date: Sat, 22 Apr 2023 18:30:46 +0400 Subject: [PATCH 6/9] Added the ability to get the current bot name in the given language as the class BotName using the method getMyName. --- telebot/__init__.py | 16 ++++++++++++++++ telebot/apihelper.py | 7 +++++++ telebot/async_telebot.py | 16 ++++++++++++++++ telebot/asyncio_helper.py | 7 +++++++ telebot/types.py | 24 ++++++++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index 549a69d..c1aaec0 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -3462,6 +3462,22 @@ class TeleBot: return apihelper.set_my_name(self.token, name, language_code) + def get_my_name(self, language_code: Optional[str]=None): + """ + Use this method to get the current bot name for the given user language. + Returns BotName on success. + + Telegram documentation: https://core.telegram.org/bots/api#getmyname + + :param language_code: Optional. A two-letter ISO 639-1 language code or an empty string + :type language_code: :obj:`str` + + :return: :class:`telebot.types.BotName` + """ + + result = apihelper.get_my_name(self.token, language_code) + return types.BotName.de_json(result) + def set_my_description(self, description: Optional[str]=None, language_code: Optional[str]=None): """ Use this method to change the bot's description, which is shown in diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 8d2b1e7..57102f9 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1205,6 +1205,13 @@ def set_my_name(token, name=None, language_code=None): payload['language_code'] = language_code return _make_request(token, method_url, params=payload, method='post') +def get_my_name(token, language_code=None): + method_url = r'getMyName' + payload = {} + if language_code is not None: + payload['language_code'] = language_code + return _make_request(token, method_url, params=payload) + def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 242e74c..a9d2ac8 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -4379,6 +4379,22 @@ class AsyncTeleBot: return await asyncio_helper.set_my_name(self.token, name, language_code) + async def get_my_name(self, language_code: Optional[str]=None): + """ + Use this method to get the current bot name for the given user language. + Returns BotName on success. + + Telegram documentation: https://core.telegram.org/bots/api#getmyname + + :param language_code: Optional. A two-letter ISO 639-1 language code or an empty string + :type language_code: :obj:`str` + + :return: :class:`telebot.types.BotName` + """ + + result = await asyncio_helper.get_my_name(self.token, language_code) + return types.BotName.de_json(result) + async def set_chat_menu_button(self, chat_id: Union[int, str]=None, menu_button: types.MenuButton=None) -> bool: """ diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 804e516..507aec0 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1193,6 +1193,13 @@ async def set_my_name(token, name=None, language_code=None): payload['language_code'] = language_code return await _process_request(token, method_url, params=payload, method='post') +async def get_my_name(token, language_code=None): + method_url = r'getMyName' + payload = {} + if language_code is not None: + payload['language_code'] = language_code + return await _process_request(token, method_url, params=payload) + async def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} diff --git a/telebot/types.py b/telebot/types.py index 53b692d..8a4460e 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -7658,3 +7658,27 @@ class SwitchInlineQueryChosenChat(JsonDeserializable, Dictionaryable, JsonSerial def to_json(self): return json.dumps(self.to_dict()) + + +class BotName(JsonDeserializable): + """ + This object represents a bot name. + + Telegram Documentation: https://core.telegram.org/bots/api#botname + + :param name: The bot name + :type name: :obj:`str` + + :return: Instance of the class + :rtype: :class:`BotName` + """ + + @classmethod + def de_json(cls, json_string): + if json_string is None: + return None + obj = cls.check_json(json_string) + return cls(**obj) + + def __init__(self, name: str): + self.name: str = name \ No newline at end of file From be69feb25284f0d7dba3d7e6a8d96c55e72ae0de Mon Sep 17 00:00:00 2001 From: coder2020official Date: Sat, 22 Apr 2023 20:38:05 +0400 Subject: [PATCH 7/9] * Added a test for message entity __html_text function. #1971 should be fixed and then todo can be done. --- tests/test_types.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_types.py b/tests/test_types.py index c696f16..8c15396 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -271,5 +271,30 @@ def test_sent_web_app_message(): assert sent_web_app_message.inline_message_id == '29430' +def test_message_entity(): + # TODO: Add support for nesting entities + + + #sample_string_1 = r'{"update_id":934522126,"message":{"message_id":1374510,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682177590,"text":"b b b","entities":[{"offset":0,"length":2,"type":"bold"},{"offset":0,"length":1,"type":"italic"},{"offset":2,"length":2,"type":"bold"},{"offset":2,"length":1,"type":"italic"},{"offset":4,"length":1,"type":"bold"},{"offset":4,"length":1,"type":"italic"}]}}' + #update = types.Update.de_json(sample_string_1) + #message: types.Message = update.message + #assert message.html_text == "b b b" + + sample_string_2 = r'{"update_id":934522166,"message":{"message_id":1374526,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179716,"text":"b b b","entities":[{"offset":0,"length":1,"type":"bold"},{"offset":2,"length":1,"type":"bold"},{"offset":4,"length":1,"type":"italic"}]}}' + message_2 = types.Update.de_json(sample_string_2).message + assert message_2.html_text == "b b b" + + + + #sample_string_3 = r'{"update_id":934522172,"message":{"message_id":1374530,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179968,"text":"This is a bold text with a nested italic and bold text.","entities":[{"offset":10,"length":4,"type":"bold"},{"offset":27,"length":7,"type":"italic"},{"offset":34,"length":15,"type":"bold"},{"offset":34,"length":15,"type":"italic"}]}}' + #message_3 = types.Update.de_json(sample_string_3).message + #assert message_3.html_text == "This is a bold text with a nested italic and bold text." + + + assert True + + + + From 26575dc5e7f25f026f03371243babca7022ca5cb Mon Sep 17 00:00:00 2001 From: coder2020official Date: Sat, 22 Apr 2023 20:51:08 +0400 Subject: [PATCH 8/9] Added support for launching Web Apps from inline query results by replacing the parameters switch_pm_text and switch_pm_parameter of the method answerInlineQuery with the parameter button of type InlineQueryResultsButton. --- telebot/__init__.py | 12 +++++++-- telebot/apihelper.py | 8 +++--- telebot/async_telebot.py | 12 +++++++-- telebot/asyncio_helper.py | 10 ++++---- telebot/types.py | 51 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index c1aaec0..0579325 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -4521,7 +4521,8 @@ class TeleBot: is_personal: Optional[bool]=None, next_offset: Optional[str]=None, switch_pm_text: Optional[str]=None, - switch_pm_parameter: Optional[str]=None) -> bool: + switch_pm_parameter: Optional[str]=None, + button: Optional[types.InlineQueryResultsButton]=None) -> bool: """ Use this method to send answers to an inline query. On success, True is returned. No more than 50 results per query are allowed. @@ -4557,11 +4558,18 @@ class TeleBot: :param switch_pm_text: Parameter for the start message sent to the bot when user presses the switch button :type switch_pm_text: :obj:`str` + :param button: A JSON-serialized object describing a button to be shown above inline query results + :type button: :obj:`types.InlineQueryResultsButton` + :return: On success, True is returned. :rtype: :obj:`bool` """ + if not button and (switch_pm_text or switch_pm_parameter): + logger.warning("switch_pm_text and switch_pm_parameter are deprecated for answer_inline_query. Use button instead.") + button = types.InlineQueryResultsButton(text=switch_pm_text, start_parameter=switch_pm_parameter) + return apihelper.answer_inline_query(self.token, inline_query_id, results, cache_time, is_personal, next_offset, - switch_pm_text, switch_pm_parameter) + button) def answer_callback_query( self, callback_query_id: int, diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 57102f9..a07f1ef 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1614,7 +1614,7 @@ def answer_callback_query(token, callback_query_id, text=None, show_alert=None, def answer_inline_query(token, inline_query_id, results, cache_time=None, is_personal=None, next_offset=None, - switch_pm_text=None, switch_pm_parameter=None): + button=None): method_url = 'answerInlineQuery' payload = {'inline_query_id': inline_query_id, 'results': _convert_list_json_serializable(results)} if cache_time is not None: @@ -1623,10 +1623,8 @@ def answer_inline_query(token, inline_query_id, results, cache_time=None, is_per payload['is_personal'] = is_personal if next_offset is not None: payload['next_offset'] = next_offset - if switch_pm_text: - payload['switch_pm_text'] = switch_pm_text - if switch_pm_parameter: - payload['switch_pm_parameter'] = switch_pm_parameter + if button is not None: + payload["button"] = button.to_json() return _make_request(token, method_url, params=payload, method='post') diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index a9d2ac8..1a86024 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -5371,7 +5371,8 @@ class AsyncTeleBot: is_personal: Optional[bool]=None, next_offset: Optional[str]=None, switch_pm_text: Optional[str]=None, - switch_pm_parameter: Optional[str]=None) -> bool: + switch_pm_parameter: Optional[str]=None, + button: Optional[types.InlineQueryResultsButton]=None) -> bool: """ Use this method to send answers to an inline query. On success, True is returned. No more than 50 results per query are allowed. @@ -5407,11 +5408,18 @@ class AsyncTeleBot: :param switch_pm_text: Parameter for the start message sent to the bot when user presses the switch button :type switch_pm_text: :obj:`str` + :param button: A JSON-serialized object describing a button to be shown above inline query results + :type button: :obj:`types.InlineQueryResultsButton` + :return: On success, True is returned. :rtype: :obj:`bool` """ + + if not button and (switch_pm_text or switch_pm_parameter): + logger.warning("switch_pm_text and switch_pm_parameter are deprecated for answer_inline_query. Use button instead.") + button = types.InlineQueryResultsButton(text=switch_pm_text, start_parameter=switch_pm_parameter) return await asyncio_helper.answer_inline_query(self.token, inline_query_id, results, cache_time, is_personal, next_offset, - switch_pm_text, switch_pm_parameter) + button) async def answer_callback_query( self, callback_query_id: int, diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 507aec0..5a615b3 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1604,7 +1604,7 @@ async def answer_callback_query(token, callback_query_id, text=None, show_alert= async def answer_inline_query(token, inline_query_id, results, cache_time=None, is_personal=None, next_offset=None, - switch_pm_text=None, switch_pm_parameter=None): + button=None): method_url = 'answerInlineQuery' payload = {'inline_query_id': inline_query_id, 'results': await _convert_list_json_serializable(results)} if cache_time is not None: @@ -1613,10 +1613,10 @@ async def answer_inline_query(token, inline_query_id, results, cache_time=None, payload['is_personal'] = is_personal if next_offset is not None: payload['next_offset'] = next_offset - if switch_pm_text: - payload['switch_pm_text'] = switch_pm_text - if switch_pm_parameter: - payload['switch_pm_parameter'] = switch_pm_parameter + if button is not None: + payload["button"] = button.to_json() + + return await _process_request(token, method_url, params=payload, method='post') diff --git a/telebot/types.py b/telebot/types.py index 8a4460e..9f5a507 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -7681,4 +7681,53 @@ class BotName(JsonDeserializable): return cls(**obj) def __init__(self, name: str): - self.name: str = name \ No newline at end of file + self.name: str = name + + +class InlineQueryResultsButton(JsonSerializable, Dictionaryable): + """ + This object represents a button to be shown above inline query results. + You must use exactly one of the optional fields. + + Telegram documentation: https://core.telegram.org/bots/api#inlinequeryresultsbutton + + :param text: Label text on the button + :type text: :obj:`str` + + :param web_app: Optional. Description of the Web App that will be launched when the user presses the button. + The Web App will be able to switch back to the inline mode using the method web_app_switch_inline_query inside the Web App. + :type web_app: :class:`telebot.types.WebAppInfo` + + :param start_parameter: Optional. Deep-linking parameter for the /start message sent to the bot when a user presses the button. + 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. + Example: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search + results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing + any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs + the bot to return an OAuth link. Once done, the bot can offer a switch_inline button so that the user can easily return to the chat + where they wanted to use the bot's inline capabilities. + :type start_parameter: :obj:`str` + + :return: Instance of the class + :rtype: :class:`InlineQueryResultsButton` + """ + + def __init__(self, text: str, web_app: Optional[WebAppInfo]=None, start_parameter: Optional[str]=None) -> None: + self.text: str = text + self.web_app: Optional[WebAppInfo] = web_app + self.start_parameter: Optional[str] = start_parameter + + + def to_dict(self) -> dict: + json_dict = { + 'text': self.text + } + + if self.web_app is not None: + json_dict['web_app'] = self.web_app.to_dict() + if self.start_parameter is not None: + json_dict['start_parameter'] = self.start_parameter + + return json_dict + + def to_json(self) -> str: + return json.dumps(self.to_dict()) \ No newline at end of file From ecb5d9b4f68c315f53843b5f3900a314245b799a Mon Sep 17 00:00:00 2001 From: coder2020official Date: Sat, 22 Apr 2023 22:53:57 +0400 Subject: [PATCH 9/9] Added tests for __html_text, fixed the bug, added custom_emoji for entities --- telebot/types.py | 32 ++++++++++++++++++++++---------- tests/test_types.py | 19 +++++++++++-------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 9f5a507..b793073 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1313,6 +1313,7 @@ class Message(JsonDeserializable): "strikethrough": "{text}", "underline": "{text}", "spoiler": "{text}", + "custom_emoji": "{text}" } if hasattr(self, "custom_subs"): @@ -1321,7 +1322,7 @@ class Message(JsonDeserializable): utf16_text = text.encode("utf-16-le") html_text = "" - def func(upd_text, subst_type=None, url=None, user=None): + def func(upd_text, subst_type=None, url=None, user=None, custom_emoji_id=None): upd_text = upd_text.decode("utf-16-le") if subst_type == "text_mention": subst_type = "text_link" @@ -1332,30 +1333,41 @@ class Message(JsonDeserializable): if not subst_type or not _subs.get(subst_type): return upd_text subs = _subs.get(subst_type) + if subst_type == "custom_emoji": + return subs.format(text=upd_text, custom_emoji_id=custom_emoji_id) return subs.format(text=upd_text, url=url) offset = 0 + start_index = 0 + end_index = 0 for entity in entities: if entity.offset > offset: + # when the offset is not 0: for example, a __b__ + # we need to add the text before the entity to the html_text html_text += func(utf16_text[offset * 2 : entity.offset * 2]) offset = entity.offset - html_text += func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user) + + new_string = func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user, entity.custom_emoji_id) + start_index = len(html_text) + html_text += new_string offset += entity.length + end_index = len(html_text) elif entity.offset == offset: - html_text += func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user) + new_string = func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user, entity.custom_emoji_id) + start_index = len(html_text) + html_text += new_string + end_index = len(html_text) offset += entity.length else: # Here we are processing nested entities. # We shouldn't update offset, because they are the same as entity before. # And, here we are replacing previous string with a new html-rendered text(previous string is already html-rendered, # And we don't change it). - entity_string = utf16_text[entity.offset * 2 : (entity.offset + entity.length) * 2] - formatted_string = func(entity_string, entity.type, entity.url, entity.user) - entity_string_decoded = entity_string.decode("utf-16-le") - last_occurence = html_text.rfind(entity_string_decoded) - string_length = len(entity_string_decoded) - #html_text = html_text.replace(html_text[last_occurence:last_occurence+string_length], formatted_string) - html_text = html_text[:last_occurence] + formatted_string + html_text[last_occurence+string_length:] + entity_string = html_text[start_index : end_index].encode("utf-16-le") + formatted_string = func(entity_string, entity.type, entity.url, entity.user, entity.custom_emoji_id).replace("&", "&").replace("<", "<").replace(">",">") + html_text = html_text[:start_index] + formatted_string + html_text[end_index:] + end_index = len(html_text) + if offset * 2 < len(utf16_text): html_text += func(utf16_text[offset * 2:]) diff --git a/tests/test_types.py b/tests/test_types.py index 8c15396..138c36b 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -275,10 +275,10 @@ def test_message_entity(): # TODO: Add support for nesting entities - #sample_string_1 = r'{"update_id":934522126,"message":{"message_id":1374510,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682177590,"text":"b b b","entities":[{"offset":0,"length":2,"type":"bold"},{"offset":0,"length":1,"type":"italic"},{"offset":2,"length":2,"type":"bold"},{"offset":2,"length":1,"type":"italic"},{"offset":4,"length":1,"type":"bold"},{"offset":4,"length":1,"type":"italic"}]}}' - #update = types.Update.de_json(sample_string_1) - #message: types.Message = update.message - #assert message.html_text == "b b b" + sample_string_1 = r'{"update_id":934522126,"message":{"message_id":1374510,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682177590,"text":"b b b","entities":[{"offset":0,"length":2,"type":"bold"},{"offset":0,"length":1,"type":"italic"},{"offset":2,"length":2,"type":"bold"},{"offset":2,"length":1,"type":"italic"},{"offset":4,"length":1,"type":"bold"},{"offset":4,"length":1,"type":"italic"}]}}' + update = types.Update.de_json(sample_string_1) + message: types.Message = update.message + assert message.html_text == "b b b" sample_string_2 = r'{"update_id":934522166,"message":{"message_id":1374526,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179716,"text":"b b b","entities":[{"offset":0,"length":1,"type":"bold"},{"offset":2,"length":1,"type":"bold"},{"offset":4,"length":1,"type":"italic"}]}}' message_2 = types.Update.de_json(sample_string_2).message @@ -286,12 +286,15 @@ def test_message_entity(): - #sample_string_3 = r'{"update_id":934522172,"message":{"message_id":1374530,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179968,"text":"This is a bold text with a nested italic and bold text.","entities":[{"offset":10,"length":4,"type":"bold"},{"offset":27,"length":7,"type":"italic"},{"offset":34,"length":15,"type":"bold"},{"offset":34,"length":15,"type":"italic"}]}}' - #message_3 = types.Update.de_json(sample_string_3).message - #assert message_3.html_text == "This is a bold text with a nested italic and bold text." + sample_string_3 = r'{"update_id":934522172,"message":{"message_id":1374530,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179968,"text":"This is a bold text with a nested italic and bold text.","entities":[{"offset":10,"length":4,"type":"bold"},{"offset":27,"length":7,"type":"italic"},{"offset":34,"length":15,"type":"bold"},{"offset":34,"length":15,"type":"italic"}]}}' + message_3 = types.Update.de_json(sample_string_3).message + assert message_3.html_text == "This is a bold text with a nested italic and bold text." - assert True + sample_string_4 = r'{"update_id":934522437,"message":{"message_id":1374619,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682189507,"forward_from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"forward_date":1682189124,"text":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa😋😋","entities":[{"offset":0,"length":76,"type":"bold"},{"offset":0,"length":76,"type":"italic"},{"offset":0,"length":76,"type":"underline"},{"offset":0,"length":76,"type":"strikethrough"},{"offset":76,"length":2,"type":"custom_emoji","custom_emoji_id":"5456188142006575553"},{"offset":78,"length":2,"type":"custom_emoji","custom_emoji_id":"5456188142006575553"}]}}' + message_4 = types.Update.de_json(sample_string_4).message + assert message_4.html_text == 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa😋😋' +