Skip to content

Commit 047838f

Browse files
Добавление Callback кнопок и update клавиатуры (#369)
1 parent 613e721 commit 047838f

File tree

5 files changed

+180
-25
lines changed

5 files changed

+180
-25
lines changed

examples/keyboard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def main():
1212

1313
keyboard = VkKeyboard(one_time=True)
1414

15-
keyboard.add_button('Белая кнопка', color=VkKeyboardColor.DEFAULT)
15+
keyboard.add_button('Белая кнопка', color=VkKeyboardColor.SECONDARY)
1616
keyboard.add_button('Зелёная кнопка', color=VkKeyboardColor.POSITIVE)
1717

1818
keyboard.add_line() # Переход на вторую строку

examples/keyboard_inline.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import json
2+
3+
from vk_api import VkApi
4+
from vk_api.utils import get_random_id
5+
from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType
6+
from vk_api.keyboard import VkKeyboard, VkKeyboardColor
7+
8+
9+
# Общие
10+
GROUP_ID = "your_group_id"
11+
GROUP_TOKEN = "your_group_token"
12+
API_VERSION = "5.120"
13+
14+
# для callback-кнопки "открыть приложение"
15+
APP_ID = 100500 # id IFrame приложения
16+
OWNER_ID = 123456789 # id владельца приложения
17+
18+
# виды callback-кнопок
19+
CALLBACK_TYPES = ("show_snackbar", "open_link", "open_app")
20+
21+
22+
def main():
23+
# Запускаем бот
24+
vk_session = VkApi(token=GROUP_TOKEN, api_version=API_VERSION)
25+
vk = vk_session.get_api()
26+
longpoll = VkBotLongPoll(vk_session, group_id=GROUP_ID)
27+
28+
# Создаем 2 клавиатуры
29+
# №1. Клавиатура с 3 кнопками: "показать всплывающее сообщение", "открыть URL" и изменить меню (свой собственный тип)
30+
keyboard_1 = VkKeyboard(one_time=False, inline=True)
31+
keyboard_1.add_callback_button(
32+
label="Покажи pop-up сообщение",
33+
color=VkKeyboardColor.SECONDARY,
34+
payload={"type": "show_snackbar", "text": "Это исчезающее сообщение на экране"},
35+
)
36+
keyboard_1.add_line()
37+
keyboard_1.add_callback_button(
38+
label="Откртыть Url",
39+
color=VkKeyboardColor.POSITIVE,
40+
payload={"type": "open_link", "link": "https://vk.com/dev/bots_docs_5"},
41+
)
42+
keyboard_1.add_line()
43+
keyboard_1.add_callback_button(
44+
label="Открыть приложение",
45+
color=VkKeyboardColor.NEGATIVE,
46+
payload={
47+
"type": "open_app",
48+
"app_id": APP_ID,
49+
"owner_id": OWNER_ID,
50+
"hash": "anything_data_100500",
51+
},
52+
)
53+
keyboard_1.add_line()
54+
keyboard_1.add_callback_button(
55+
label="Добавить красного ",
56+
color=VkKeyboardColor.PRIMARY,
57+
payload={"type": "my_own_100500_type_edit"},
58+
)
59+
60+
# №2. Клавиатура с одной красной callback-кнопкой. Нажатие изменяет меню на предыдущее.
61+
keyboard_2 = VkKeyboard(one_time=False, inline=True)
62+
keyboard_2.add_callback_button(
63+
"Назад",
64+
color=VkKeyboardColor.NEGATIVE,
65+
payload={"type": "my_own_100500_type_edit"},
66+
)
67+
68+
# Запускаем пуллинг
69+
f_toggle: bool = False
70+
for event in longpoll.listen():
71+
# отправляем меню 1го вида на любое текстовое сообщение от пользователя
72+
if event.type == VkBotEventType.MESSAGE_NEW:
73+
if event.obj.message["text"] != "":
74+
if event.from_user:
75+
# Если клиент пользователя не поддерживает callback-кнопки, нажатие на них будет отправлять текстовые
76+
# сообщения. Т.е. они будут работать как обычные inline кнопки.
77+
if "callback" not in event.obj.client_info["button_actions"]:
78+
print(
79+
f'Клиент user_id{event.obj.message["from_id"]} не поддерживает callback-кнопки.'
80+
)
81+
82+
vk.messages.send(
83+
user_id=event.obj.message["from_id"],
84+
random_id=get_random_id(),
85+
peer_id=event.obj.message["from_id"],
86+
keyboard=keyboard_1.get_keyboard(),
87+
message="Меню #1",
88+
)
89+
# обрабатываем клики по callback кнопкам
90+
elif event.type == VkBotEventType.MESSAGE_EVENT:
91+
if event.object.payload.get("type") in CALLBACK_TYPES:
92+
r = vk.messages.sendMessageEventAnswer(
93+
event_id=event.object.event_id,
94+
user_id=event.object.user_id,
95+
peer_id=event.object.peer_id,
96+
event_data=json.dumps(event.object.payload),
97+
)
98+
elif event.object.payload.get("type") == "my_own_100500_type_edit":
99+
last_id = vk.messages.edit(
100+
peer_id=event.obj.peer_id,
101+
message="Меню #2",
102+
conversation_message_id=event.obj.conversation_message_id,
103+
keyboard=(keyboard_1 if f_toggle else keyboard_2).get_keyboard(),
104+
)
105+
f_toggle = not f_toggle
106+
107+
108+
if __name__ == "__main__":
109+
main()

tests/test_keyboard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
def test_keyboard():
2828
keyboard.add_button(
2929
'Test-1',
30-
color=VkKeyboardColor.DEFAULT,
30+
color=VkKeyboardColor.SECONDARY,
3131
payload={'test': 'some_payload'}
3232
)
3333
keyboard.add_line()

vk_api/bot_longpoll.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class VkBotEventType(Enum):
2222
MESSAGE_NEW = 'message_new'
2323
MESSAGE_REPLY = 'message_reply'
2424
MESSAGE_EDIT = 'message_edit'
25+
MESSAGE_EVENT = 'message_event'
2526

2627
MESSAGE_TYPING_STATE = 'message_typing_state'
2728

vk_api/keyboard.py

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from .utils import sjson_dumps
1313

1414

15-
15+
MAX_BUTTONS_ON_LINE = 5
16+
MAX_DEFAULT_LINES = 10
17+
MAX_INLINE_LINES = 6
1618

1719

1820
class VkKeyboardColor(Enum):
@@ -22,14 +24,15 @@ class VkKeyboardColor(Enum):
2224
PRIMARY = 'primary'
2325

2426
#: Белая
25-
DEFAULT = 'default'
27+
SECONDARY = 'secondary'
2628

2729
#: Красная
2830
NEGATIVE = 'negative'
2931

3032
#: Зелёная
3133
POSITIVE = 'positive'
3234

35+
3336
class VkKeyboardButton(Enum):
3437
""" Возможные типы кнопки """
3538

@@ -44,10 +47,12 @@ class VkKeyboardButton(Enum):
4447

4548
#: Кнопка с приложением VK Apps
4649
VKAPPS = "open_app"
47-
50+
4851
#: Кнопка с ссылкой
4952
OPENLINK = "open_link"
5053

54+
#: Callback-кнопка
55+
CALLBACK = "callback"
5156

5257

5358
class VkKeyboard(object):
@@ -82,9 +87,9 @@ def get_empty_keyboard(cls):
8287
keyboard.keyboard['buttons'] = []
8388
return keyboard.get_keyboard()
8489

85-
def add_button(self, label, color=VkKeyboardColor.DEFAULT, payload=None):
90+
def add_button(self, label, color=VkKeyboardColor.SECONDARY, payload=None):
8691
""" Добавить кнопку с текстом.
87-
Максимальное количество кнопок на строке - 4
92+
Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE
8893
8994
:param label: Надпись на кнопке и текст, отправляющийся при её нажатии.
9095
:type label: str
@@ -96,8 +101,8 @@ def add_button(self, label, color=VkKeyboardColor.DEFAULT, payload=None):
96101

97102
current_line = self.lines[-1]
98103

99-
if len(current_line) >= 4:
100-
raise ValueError('Max 4 buttons on a line')
104+
if len(current_line) >= MAX_BUTTONS_ON_LINE:
105+
raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line')
101106

102107
color_value = color
103108

@@ -118,6 +123,42 @@ def add_button(self, label, color=VkKeyboardColor.DEFAULT, payload=None):
118123
}
119124
})
120125

126+
def add_callback_button(self, label, color=VkKeyboardColor.SECONDARY, payload=None):
127+
""" Добавить callback-кнопку с текстом.
128+
Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE
129+
130+
:param label: Надпись на кнопке и текст, отправляющийся при её нажатии.
131+
:type label: str
132+
:param color: цвет кнопки.
133+
:type color: VkKeyboardColor or str
134+
:param payload: Параметр для callback api
135+
:type payload: str or list or dict
136+
"""
137+
138+
current_line = self.lines[-1]
139+
140+
if len(current_line) >= MAX_BUTTONS_ON_LINE:
141+
raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line')
142+
143+
color_value = color
144+
145+
if isinstance(color, VkKeyboardColor):
146+
color_value = color_value.value
147+
148+
if payload is not None and not isinstance(payload, six.string_types):
149+
payload = sjson_dumps(payload)
150+
151+
button_type = VkKeyboardButton.CALLBACK.value
152+
153+
current_line.append({
154+
'color': color_value,
155+
'action': {
156+
'type': button_type,
157+
'payload': payload,
158+
'label': label,
159+
}
160+
})
161+
121162
def add_location_button(self, payload=None):
122163
""" Добавить кнопку с местоположением.
123164
Всегда занимает всю ширину линии.
@@ -130,8 +171,8 @@ def add_location_button(self, payload=None):
130171

131172
if len(current_line) != 0:
132173
raise ValueError(
133-
'This type of button takes the entire width of the line'
134-
)
174+
'This type of button takes the entire width of the line'
175+
)
135176

136177
if payload is not None and not isinstance(payload, six.string_types):
137178
payload = sjson_dumps(payload)
@@ -160,8 +201,8 @@ def add_vkpay_button(self, hash, payload=None):
160201

161202
if len(current_line) != 0:
162203
raise ValueError(
163-
'This type of button takes the entire width of the line'
164-
)
204+
'This type of button takes the entire width of the line'
205+
)
165206

166207
if payload is not None and not isinstance(payload, six.string_types):
167208
payload = sjson_dumps(payload)
@@ -198,8 +239,8 @@ def add_vkapps_button(self, app_id, owner_id, label, hash, payload=None):
198239

199240
if len(current_line) != 0:
200241
raise ValueError(
201-
'This type of button takes the entire width of the line'
202-
)
242+
'This type of button takes the entire width of the line'
243+
)
203244

204245
if payload is not None and not isinstance(payload, six.string_types):
205246
payload = sjson_dumps(payload)
@@ -216,10 +257,10 @@ def add_vkapps_button(self, app_id, owner_id, label, hash, payload=None):
216257
'hash': hash
217258
}
218259
})
219-
260+
220261
def add_openlink_button(self, label, link, payload=None):
221262
""" Добавить кнопку с ссылкой
222-
Максимальное количество кнопок на строке - 4
263+
Максимальное количество кнопок на строке - MAX_BUTTONS_ON_LINE
223264
224265
:param label: Надпись на кнопке
225266
:type label: str
@@ -230,8 +271,8 @@ def add_openlink_button(self, label, link, payload=None):
230271
"""
231272
current_line = self.lines[-1]
232273

233-
if len(current_line) >= 4:
234-
raise ValueError('Max 4 buttons on a line')
274+
if len(current_line) >= MAX_BUTTONS_ON_LINE:
275+
raise ValueError(f'Max {MAX_BUTTONS_ON_LINE} buttons on a line')
235276

236277
if payload is not None and not isinstance(payload, six.string_types):
237278
payload = sjson_dumps(payload)
@@ -241,19 +282,23 @@ def add_openlink_button(self, label, link, payload=None):
241282
current_line.append({
242283
'action': {
243284
'type': button_type,
244-
'link' : link,
285+
'link': link,
245286
'label': label,
246287
'payload': payload
247288
}
248289
})
249-
250290

251291
def add_line(self):
252292
""" Создаёт новую строку, на которой можно размещать кнопки.
253-
Максимальное количество строк - 10.
293+
Максимальное количество строк:
294+
Стандартное отображение - MAX_DEFAULT_LINES;
295+
Inline-отображение - MAX_INLINE_LINES.
254296
"""
255-
256-
if len(self.lines) >= 10:
257-
raise ValueError('Max 10 lines')
297+
if self.inline:
298+
if len(self.lines) >= MAX_INLINE_LINES:
299+
raise ValueError(f'Max {MAX_INLINE_LINES} lines for inline keyboard')
300+
else:
301+
if len(self.lines) >= MAX_DEFAULT_LINES:
302+
raise ValueError(f'Max {MAX_DEFAULT_LINES} lines for default keyboard')
258303

259304
self.lines.append([])

0 commit comments

Comments
 (0)