From 8422af22478e832ffd150a5deb9dce5ab70b889f Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 24 Jul 2020 16:19:30 +0300 Subject: [PATCH 1/5] apply "patch-1" by szy13 (+ add fixes in doc strings) --- vk_api/keyboard.py | 54 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/vk_api/keyboard.py b/vk_api/keyboard.py index 8b4fcb44..10bea4d7 100644 --- a/vk_api/keyboard.py +++ b/vk_api/keyboard.py @@ -12,7 +12,9 @@ from .utils import sjson_dumps - +MAX_BUTTONS_ON_LINE = 5 +MAX_DEFAULT_LINES = 10 +MAX_INLINE_LINES = 6 class VkKeyboardColor(Enum): @@ -22,7 +24,7 @@ class VkKeyboardColor(Enum): PRIMARY = 'primary' #: Белая - DEFAULT = 'default' + SECONDARY = 'secondary' #: Красная NEGATIVE = 'negative' @@ -30,6 +32,7 @@ class VkKeyboardColor(Enum): #: Зелёная POSITIVE = 'positive' + class VkKeyboardButton(Enum): """ Возможные типы кнопки """ @@ -44,12 +47,11 @@ class VkKeyboardButton(Enum): #: Кнопка с приложением VK Apps VKAPPS = "open_app" - + #: Кнопка с ссылкой OPENLINK = "open_link" - class VkKeyboard(object): """ Класс для создания клавиатуры для бота (https://vk.com/dev/bots_docs_3) :param one_time: Если True, клавиатура исчезнет после нажатия на кнопку @@ -82,9 +84,9 @@ def get_empty_keyboard(cls): keyboard.keyboard['buttons'] = [] return keyboard.get_keyboard() - def add_button(self, label, color=VkKeyboardColor.DEFAULT, payload=None): + def add_button(self, label, color=VkKeyboardColor.SECONDARY, payload=None): """ Добавить кнопку с текстом. - Максимальное количество кнопок на строке - 4 + Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE :param label: Надпись на кнопке и текст, отправляющийся при её нажатии. :type label: str @@ -96,8 +98,8 @@ def add_button(self, label, color=VkKeyboardColor.DEFAULT, payload=None): current_line = self.lines[-1] - if len(current_line) >= 4: - raise ValueError('Max 4 buttons on a line') + if len(current_line) >= MAX_BUTTONS_ON_LINE: + raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line') color_value = color @@ -130,8 +132,8 @@ def add_location_button(self, payload=None): if len(current_line) != 0: raise ValueError( - 'This type of button takes the entire width of the line' - ) + 'This type of button takes the entire width of the line' + ) if payload is not None and not isinstance(payload, six.string_types): payload = sjson_dumps(payload) @@ -160,8 +162,8 @@ def add_vkpay_button(self, hash, payload=None): if len(current_line) != 0: raise ValueError( - 'This type of button takes the entire width of the line' - ) + 'This type of button takes the entire width of the line' + ) if payload is not None and not isinstance(payload, six.string_types): payload = sjson_dumps(payload) @@ -198,8 +200,8 @@ def add_vkapps_button(self, app_id, owner_id, label, hash, payload=None): if len(current_line) != 0: raise ValueError( - 'This type of button takes the entire width of the line' - ) + 'This type of button takes the entire width of the line' + ) if payload is not None and not isinstance(payload, six.string_types): payload = sjson_dumps(payload) @@ -216,10 +218,10 @@ def add_vkapps_button(self, app_id, owner_id, label, hash, payload=None): 'hash': hash } }) - + def add_openlink_button(self, label, link, payload=None): """ Добавить кнопку с ссылкой - Максимальное количество кнопок на строке - 4 + Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE :param label: Надпись на кнопке :type label: str @@ -230,8 +232,8 @@ def add_openlink_button(self, label, link, payload=None): """ current_line = self.lines[-1] - if len(current_line) >= 4: - raise ValueError('Max 4 buttons on a line') + if len(current_line) >= MAX_BUTTONS_ON_LINE: + raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line') if payload is not None and not isinstance(payload, six.string_types): payload = sjson_dumps(payload) @@ -241,19 +243,23 @@ def add_openlink_button(self, label, link, payload=None): current_line.append({ 'action': { 'type': button_type, - 'link' : link, + 'link': link, 'label': label, 'payload': payload } }) - def add_line(self): """ Создаёт новую строку, на которой можно размещать кнопки. - Максимальное количество строк - 10. + Максимальное количество строк: + Стандартное отображение - MAX_DEFAULT_LINES; + Inline-отображение - MAX_INLINE_LINES. """ - - if len(self.lines) >= 10: - raise ValueError('Max 10 lines') + if self.inline: + if len(self.lines) >= MAX_INLINE_LINES: + raise ValueError(f'Max {MAX_INLINE_LINES} lines for inline keyboard') + else: + if len(self.lines) >= MAX_DEFAULT_LINES: + raise ValueError(f'Max {MAX_DEFAULT_LINES} lines for default keyboard') self.lines.append([]) From 12a750f800d8248366dc3037fbca9197726843b0 Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 24 Jul 2020 16:25:46 +0300 Subject: [PATCH 2/5] add callback buttons --- vk_api/bot_longpoll.py | 1 + vk_api/keyboard.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/vk_api/bot_longpoll.py b/vk_api/bot_longpoll.py index 80b98aae..e3943a10 100644 --- a/vk_api/bot_longpoll.py +++ b/vk_api/bot_longpoll.py @@ -22,6 +22,7 @@ class VkBotEventType(Enum): MESSAGE_NEW = 'message_new' MESSAGE_REPLY = 'message_reply' MESSAGE_EDIT = 'message_edit' + MESSAGE_EVENT = 'message_event' MESSAGE_TYPING_STATE = 'message_typing_state' diff --git a/vk_api/keyboard.py b/vk_api/keyboard.py index 10bea4d7..a6c4fd68 100644 --- a/vk_api/keyboard.py +++ b/vk_api/keyboard.py @@ -51,6 +51,9 @@ class VkKeyboardButton(Enum): #: Кнопка с ссылкой OPENLINK = "open_link" + #: Callback-кнопка + CALLBACK = "callback" + class VkKeyboard(object): """ Класс для создания клавиатуры для бота (https://vk.com/dev/bots_docs_3) @@ -120,6 +123,42 @@ def add_button(self, label, color=VkKeyboardColor.SECONDARY, payload=None): } }) + def add_callback_button(self, label, color=VkKeyboardColor.SECONDARY, payload=None): + """ Добавить callback-кнопку с текстом. + Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE + + :param label: Надпись на кнопке и текст, отправляющийся при её нажатии. + :type label: str + :param color: цвет кнопки. + :type color: VkKeyboardColor or str + :param payload: Параметр для callback api + :type payload: str or list or dict + """ + + current_line = self.lines[-1] + + if len(current_line) >= MAX_BUTTONS_ON_LINE: + raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line') + + color_value = color + + if isinstance(color, VkKeyboardColor): + color_value = color_value.value + + if payload is not None and not isinstance(payload, six.string_types): + payload = sjson_dumps(payload) + + button_type = VkKeyboardButton.CALLBACK.value + + current_line.append({ + 'color': color_value, + 'action': { + 'type': button_type, + 'payload': payload, + 'label': label, + } + }) + def add_location_button(self, payload=None): """ Добавить кнопку с местоположением. Всегда занимает всю ширину линии. From 6021bc96919099019b37c3fc038218f78a56e8da Mon Sep 17 00:00:00 2001 From: michael Date: Sat, 25 Jul 2020 15:24:35 +0300 Subject: [PATCH 3/5] fix example and test --- examples/keyboard.py | 2 +- tests/test_keyboard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/keyboard.py b/examples/keyboard.py index 0f8ab594..9378edad 100644 --- a/examples/keyboard.py +++ b/examples/keyboard.py @@ -12,7 +12,7 @@ def main(): keyboard = VkKeyboard(one_time=True) - keyboard.add_button('Белая кнопка', color=VkKeyboardColor.DEFAULT) + keyboard.add_button('Белая кнопка', color=VkKeyboardColor.SECONDARY) keyboard.add_button('Зелёная кнопка', color=VkKeyboardColor.POSITIVE) keyboard.add_line() # Переход на вторую строку diff --git a/tests/test_keyboard.py b/tests/test_keyboard.py index 9ecee187..6eea548a 100644 --- a/tests/test_keyboard.py +++ b/tests/test_keyboard.py @@ -27,7 +27,7 @@ def test_keyboard(): keyboard.add_button( 'Test-1', - color=VkKeyboardColor.DEFAULT, + color=VkKeyboardColor.SECONDARY, payload={'test': 'some_payload'} ) keyboard.add_line() From 1014cd300d4deaa04131e4f118df81800e36184d Mon Sep 17 00:00:00 2001 From: michael Date: Sat, 25 Jul 2020 15:24:47 +0300 Subject: [PATCH 4/5] add example for callback buttons --- examples/keyboard_inline.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 examples/keyboard_inline.py diff --git a/examples/keyboard_inline.py b/examples/keyboard_inline.py new file mode 100644 index 00000000..d76d35c9 --- /dev/null +++ b/examples/keyboard_inline.py @@ -0,0 +1,83 @@ +from vk_api import VkApi +from vk_api.utils import get_random_id +from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType +from vk_api.keyboard import VkKeyboard, VkKeyboardColor +import json + + +# Общие +GROUP_ID = '1559*****' +GROUP_TOKEN = '3d8326df2148cc843d8326df214...здесь токен группы...becf3547' +API_VERSION = '5.120' + +# для callback-кнопки "открыть приложение" +APP_ID = 100500 # id IFrame приложения +OWNER_ID = 123456789 # id владельца приложения + +# виды callback-кнопок +CALLBACK_TYPES = ('show_snackbar', 'open_link', 'open_app') + + +# Запускаем бот +vk_session = VkApi(token=GROUP_TOKEN, api_version=API_VERSION) +vk = vk_session.get_api() +longpoll = VkBotLongPoll(vk_session, group_id=GROUP_ID) + + +# Создаем 2 клавиатуры +settings = dict(one_time=False, inline=True) +# №1. Клавиатура с 3 кнопками: "показать всплывающее сообщение", "открыть URL" и изменить меню (свой собственный тип) +keyboard_1 = VkKeyboard(**settings) +keyboard_1.add_callback_button(label='Покажи pop-up сообщение', color=VkKeyboardColor.SECONDARY, + payload={"type": "show_snackbar", "text": "Это исчезающее сообщение на экране"}) +keyboard_1.add_line() +keyboard_1.add_callback_button(label='Откртыть Url', color=VkKeyboardColor.POSITIVE, + payload={"type": "open_link", "link": "https://vk.com/dev/bots_docs_5"}) +keyboard_1.add_line() +keyboard_1.add_callback_button(label='Открыть приложение', color=VkKeyboardColor.NEGATIVE, + payload={"type": "open_app", "app_id": APP_ID, "owner_id": OWNER_ID, + "hash": "anything_data_100500"}) +keyboard_1.add_line() +keyboard_1.add_callback_button(label='Добавить красного ', color=VkKeyboardColor.PRIMARY, + payload={"type": "my_own_100500_type_edit"}) + +# №2. Клавиатура с одной красной callback-кнопкой. Нажатие изменяет меню на предыдущее. +keyboard_2 = VkKeyboard(**settings) +keyboard_2.add_callback_button('Назад', color=VkKeyboardColor.NEGATIVE, payload={"type": "my_own_100500_type_edit"}) + + +# Запускаем пуллинг +f_toggle: bool = False +for event in longpoll.listen(): + # отправляем меню 1го вида на любое текстовое сообщение от пользователя + if event.type == VkBotEventType.MESSAGE_NEW: + if event.obj.message['text'] != '': + if event.from_user: + # Если клиент пользователя не поддерживает callback-кнопки, нажатие на них будет отправлять текстовые + # сообщения. Т.е. они будут работать как обычные inline кнопки. + if 'callback' not in event.obj.client_info['button_actions']: + print(f'Клиент user_id{event.obj.message["from_id"]} не поддерживает callback-кнопки.') + + vk.messages.send( + user_id=event.obj.message['from_id'], + random_id=get_random_id(), + peer_id=event.obj.message['from_id'], + keyboard=keyboard_1.get_keyboard(), + message='Меню #1') + # обрабатываем клики по callback кнопкам + elif event.type == VkBotEventType.MESSAGE_EVENT: + if event.object.payload.get('type') in CALLBACK_TYPES: + r = vk.messages.sendMessageEventAnswer(event_id=event.object.event_id, user_id=event.object.user_id, + peer_id=event.object.peer_id, + event_data=json.dumps(event.object.payload)) + elif event.object.payload.get('type') == 'my_own_100500_type_edit': + last_id = vk.messages.edit( + peer_id=event.obj.peer_id, + message='Меню #2', + conversation_message_id=event.obj.conversation_message_id, + keyboard=(keyboard_1 if f_toggle else keyboard_2).get_keyboard()) + f_toggle = not f_toggle + + +if __name__ == '__main__': + print() From 0d556f099aaf7601e4e3194047e4bf04175259fc Mon Sep 17 00:00:00 2001 From: python273 Date: Fri, 11 Sep 2020 16:27:05 +0300 Subject: [PATCH 5/5] Reformat examples/keyboard_inline.py --- examples/keyboard_inline.py | 150 +++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 62 deletions(-) diff --git a/examples/keyboard_inline.py b/examples/keyboard_inline.py index d76d35c9..c2970426 100644 --- a/examples/keyboard_inline.py +++ b/examples/keyboard_inline.py @@ -1,83 +1,109 @@ +import json + from vk_api import VkApi from vk_api.utils import get_random_id from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType from vk_api.keyboard import VkKeyboard, VkKeyboardColor -import json # Общие -GROUP_ID = '1559*****' -GROUP_TOKEN = '3d8326df2148cc843d8326df214...здесь токен группы...becf3547' -API_VERSION = '5.120' +GROUP_ID = "your_group_id" +GROUP_TOKEN = "your_group_token" +API_VERSION = "5.120" # для callback-кнопки "открыть приложение" -APP_ID = 100500 # id IFrame приложения -OWNER_ID = 123456789 # id владельца приложения +APP_ID = 100500 # id IFrame приложения +OWNER_ID = 123456789 # id владельца приложения # виды callback-кнопок -CALLBACK_TYPES = ('show_snackbar', 'open_link', 'open_app') - - -# Запускаем бот -vk_session = VkApi(token=GROUP_TOKEN, api_version=API_VERSION) -vk = vk_session.get_api() -longpoll = VkBotLongPoll(vk_session, group_id=GROUP_ID) +CALLBACK_TYPES = ("show_snackbar", "open_link", "open_app") -# Создаем 2 клавиатуры -settings = dict(one_time=False, inline=True) -# №1. Клавиатура с 3 кнопками: "показать всплывающее сообщение", "открыть URL" и изменить меню (свой собственный тип) -keyboard_1 = VkKeyboard(**settings) -keyboard_1.add_callback_button(label='Покажи pop-up сообщение', color=VkKeyboardColor.SECONDARY, - payload={"type": "show_snackbar", "text": "Это исчезающее сообщение на экране"}) -keyboard_1.add_line() -keyboard_1.add_callback_button(label='Откртыть Url', color=VkKeyboardColor.POSITIVE, - payload={"type": "open_link", "link": "https://vk.com/dev/bots_docs_5"}) -keyboard_1.add_line() -keyboard_1.add_callback_button(label='Открыть приложение', color=VkKeyboardColor.NEGATIVE, - payload={"type": "open_app", "app_id": APP_ID, "owner_id": OWNER_ID, - "hash": "anything_data_100500"}) -keyboard_1.add_line() -keyboard_1.add_callback_button(label='Добавить красного ', color=VkKeyboardColor.PRIMARY, - payload={"type": "my_own_100500_type_edit"}) +def main(): + # Запускаем бот + vk_session = VkApi(token=GROUP_TOKEN, api_version=API_VERSION) + vk = vk_session.get_api() + longpoll = VkBotLongPoll(vk_session, group_id=GROUP_ID) -# №2. Клавиатура с одной красной callback-кнопкой. Нажатие изменяет меню на предыдущее. -keyboard_2 = VkKeyboard(**settings) -keyboard_2.add_callback_button('Назад', color=VkKeyboardColor.NEGATIVE, payload={"type": "my_own_100500_type_edit"}) + # Создаем 2 клавиатуры + # №1. Клавиатура с 3 кнопками: "показать всплывающее сообщение", "открыть URL" и изменить меню (свой собственный тип) + keyboard_1 = VkKeyboard(one_time=False, inline=True) + keyboard_1.add_callback_button( + label="Покажи pop-up сообщение", + color=VkKeyboardColor.SECONDARY, + payload={"type": "show_snackbar", "text": "Это исчезающее сообщение на экране"}, + ) + keyboard_1.add_line() + keyboard_1.add_callback_button( + label="Откртыть Url", + color=VkKeyboardColor.POSITIVE, + payload={"type": "open_link", "link": "https://vk.com/dev/bots_docs_5"}, + ) + keyboard_1.add_line() + keyboard_1.add_callback_button( + label="Открыть приложение", + color=VkKeyboardColor.NEGATIVE, + payload={ + "type": "open_app", + "app_id": APP_ID, + "owner_id": OWNER_ID, + "hash": "anything_data_100500", + }, + ) + keyboard_1.add_line() + keyboard_1.add_callback_button( + label="Добавить красного ", + color=VkKeyboardColor.PRIMARY, + payload={"type": "my_own_100500_type_edit"}, + ) + # №2. Клавиатура с одной красной callback-кнопкой. Нажатие изменяет меню на предыдущее. + keyboard_2 = VkKeyboard(one_time=False, inline=True) + keyboard_2.add_callback_button( + "Назад", + color=VkKeyboardColor.NEGATIVE, + payload={"type": "my_own_100500_type_edit"}, + ) -# Запускаем пуллинг -f_toggle: bool = False -for event in longpoll.listen(): - # отправляем меню 1го вида на любое текстовое сообщение от пользователя - if event.type == VkBotEventType.MESSAGE_NEW: - if event.obj.message['text'] != '': - if event.from_user: - # Если клиент пользователя не поддерживает callback-кнопки, нажатие на них будет отправлять текстовые - # сообщения. Т.е. они будут работать как обычные inline кнопки. - if 'callback' not in event.obj.client_info['button_actions']: - print(f'Клиент user_id{event.obj.message["from_id"]} не поддерживает callback-кнопки.') + # Запускаем пуллинг + f_toggle: bool = False + for event in longpoll.listen(): + # отправляем меню 1го вида на любое текстовое сообщение от пользователя + if event.type == VkBotEventType.MESSAGE_NEW: + if event.obj.message["text"] != "": + if event.from_user: + # Если клиент пользователя не поддерживает callback-кнопки, нажатие на них будет отправлять текстовые + # сообщения. Т.е. они будут работать как обычные inline кнопки. + if "callback" not in event.obj.client_info["button_actions"]: + print( + f'Клиент user_id{event.obj.message["from_id"]} не поддерживает callback-кнопки.' + ) - vk.messages.send( - user_id=event.obj.message['from_id'], + vk.messages.send( + user_id=event.obj.message["from_id"], random_id=get_random_id(), - peer_id=event.obj.message['from_id'], + peer_id=event.obj.message["from_id"], keyboard=keyboard_1.get_keyboard(), - message='Меню #1') - # обрабатываем клики по callback кнопкам - elif event.type == VkBotEventType.MESSAGE_EVENT: - if event.object.payload.get('type') in CALLBACK_TYPES: - r = vk.messages.sendMessageEventAnswer(event_id=event.object.event_id, user_id=event.object.user_id, - peer_id=event.object.peer_id, - event_data=json.dumps(event.object.payload)) - elif event.object.payload.get('type') == 'my_own_100500_type_edit': - last_id = vk.messages.edit( - peer_id=event.obj.peer_id, - message='Меню #2', - conversation_message_id=event.obj.conversation_message_id, - keyboard=(keyboard_1 if f_toggle else keyboard_2).get_keyboard()) - f_toggle = not f_toggle + message="Меню #1", + ) + # обрабатываем клики по callback кнопкам + elif event.type == VkBotEventType.MESSAGE_EVENT: + if event.object.payload.get("type") in CALLBACK_TYPES: + r = vk.messages.sendMessageEventAnswer( + event_id=event.object.event_id, + user_id=event.object.user_id, + peer_id=event.object.peer_id, + event_data=json.dumps(event.object.payload), + ) + elif event.object.payload.get("type") == "my_own_100500_type_edit": + last_id = vk.messages.edit( + peer_id=event.obj.peer_id, + message="Меню #2", + conversation_message_id=event.obj.conversation_message_id, + keyboard=(keyboard_1 if f_toggle else keyboard_2).get_keyboard(), + ) + f_toggle = not f_toggle -if __name__ == '__main__': - print() +if __name__ == "__main__": + main()