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/examples/keyboard_inline.py b/examples/keyboard_inline.py new file mode 100644 index 00000000..c2970426 --- /dev/null +++ b/examples/keyboard_inline.py @@ -0,0 +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 + + +# Общие +GROUP_ID = "your_group_id" +GROUP_TOKEN = "your_group_token" +API_VERSION = "5.120" + +# для callback-кнопки "открыть приложение" +APP_ID = 100500 # id IFrame приложения +OWNER_ID = 123456789 # id владельца приложения + +# виды callback-кнопок +CALLBACK_TYPES = ("show_snackbar", "open_link", "open_app") + + +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 клавиатуры + # №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-кнопки.' + ) + + 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__": + main() 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() 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 8b4fcb44..a6c4fd68 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,10 +47,12 @@ class VkKeyboardButton(Enum): #: Кнопка с приложением VK Apps VKAPPS = "open_app" - + #: Кнопка с ссылкой OPENLINK = "open_link" + #: Callback-кнопка + CALLBACK = "callback" class VkKeyboard(object): @@ -82,9 +87,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 +101,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 @@ -118,6 +123,42 @@ def add_button(self, label, color=VkKeyboardColor.DEFAULT, 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): """ Добавить кнопку с местоположением. Всегда занимает всю ширину линии. @@ -130,8 +171,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 +201,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 +239,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 +257,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 +271,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 +282,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([])