В этом посте рассмотрим одну фичу, которая может быть полезна дорогому читателю
Недавно встал вопрос с множественным выбором. Бот предлагает пользователю список возможных вариантов, а пользователь в свою очередь имеет возможность выбрать несколько из этих вариантов. При том реализовано это через инлайн-клавиатуру
Ожидаемый результат работы флагов будет примерно следующий:
Клавиатура с двумя выбранными вариантами
Краткая вводная дана, переходим к порядку реализации.
Создаем бота через @BotFather, создаем новую ГТ и открываем AppsScripts (подробнее про создание ботов можно почитать тут.
Настоятельно рекомендую поступать также.
В файл Global записываю глобальные переменные или константы.
const API = "Ваш АПИ";
const DOC = SpreadsheetApp.openById("Ваш ИД");
const Test = DOC.getSheetByName("Ваше название листа");
//в кавычки пропишите свои значения
Здесь
Выделенное является ид гугл таблицы
Моя клава выглядит так:
let FLAGS =
{
"inline_keyboard": [
[{"text": " Уточнение деталей у клиента по вакансии устно", "callback_data": "0"}],
[{"text": " Уточнение деталей у клиента по вакансии письменно", "callback_data": "1"}],
[{"text": " Составление карты поиска", "callback_data": "2"}],
[{"text": " Размещение вакансий на различных источниках", "callback_data": "3"}],
[{"text": " Поиск на открытых источниках", "callback_data": "4"}],
[{"text": " Сорсинг", "callback_data": "5"}],
[{"text": " Телефонное интервью", "callback_data": "6"}],
[{"text": " Собеседование с видео по zoom/Skype", "callback_data": "7"}],
[{"text": " Технический скрининг на собеседование", "callback_data": "8"}],
[{"text": " Контроль выхода кандидата ", "callback_data": "9"}],
[{"text": " Контроль ИС", "callback_data": "10"}]
],
"resize_keyboard": true
};
Сейчас самое интересное…
Какая логика? Мы отправляем клаву в чат по запросу пользователя, по команде, например. Далее пользователь кликает на один из вариантов (кнопку), который хочет выбрать. Мы запоминаем его выбор и отправляем новую клавиатуру, где в тексте выбранной кнопки будет изменен флаг.
Внимательный читатель заметил, что в названиях кнопок есть смайлики. Они-то и будут нашими флагами, которые отражают одно из значений true/false, 1/0, да/нет.
Так как мы записываем клаву в таблицу, сначала будем проверять, есть ли уже эта клава в таблице и в какой именно ячейке она записана. Искать будем по ключу chat_id
function getInd(chat_id,sheet) { //возвращает индекс строки, в кот нах-ся ид
let lr = sheet.getLastRow();
let chat_id_arr = sheet.getRange(1,1,lr).getValues();
chat_id_arr = chat_id_arr.flat();
let ind = chat_id_arr.indexOf(chat_id);
return ind;
}
Функция выше возвращает id строки таблицы, в которой записан искомый ид чата. Id строки в таблице и номер строки - не одно и то же. Когда мы забираем все значения из таблицы и записываем их в массив (методом getValues()), значения в массиве начинаются с 0, тогда как в таблице отсчет ведется с 1. Просто помним об этом)
Следующая функция ищет chat_id в таблице и возвращает соответствующую этому ид клаву. В ином случае возвращает дефолтную клаву.
function getKeyboard(chat_id) { //забирает клаву из табл
let ind = getInd(chat_id,Test);
let lc = Test.getLastColumn();
let cur_keyboard = [];
let keys = [];
let KEYBOARD = {};
if (ind > 0) { //если ind=-1, chat_id не был найден в таблице
keys = Test.getRange(ind+1,2,1,lc).getValues();
keys = keys.flat();
for (i=0; i<keys.length; i++) {
cur_keyboard.push([{"text":keys, "callback_data":i}]);
}
if (cur_keyboard != "") {
KEYBOARD =
{
"inline_keyboard": cur_keyboard,
"resize_keyboard": true
}
} else {
KEYBOARD = FLAGS;
}
} else {
KEYBOARD = FLAGS;
}
return KEYBOARD
}
И разумеется, нужна функция для сохранения клавиатуры в таблице гугл
function setKeyboard(chat_id,vote) { //записывает текст клавы в табл
let ind = getInd(chat_id,Test);
let KEYBOARD = getKeyboard(chat_id);
let key = KEYBOARD.inline_keyboard[vote][0].text;
let flag = key.split(' ');
switch (flag[0])
{
case '' : flag.splice(0,1,''); break;
case '' : flag.splice(0,1,''); break;
}
flag = flag.join(' ');
KEYBOARD.inline_keyboard[vote][0].text = flag;
let new_arr = [];
new_arr = KEYBOARD.inline_keyboard.flat();
if (ind < 0) { //если ид нет в табл
let new_ind = Test.getLastRow()+1; //строка для записи нового ид
Test.getRange(new_ind,1).setValue(chat_id);
for (i=0; i<new_arr.length; i++) {
Test.getRange(new_ind,2+i).setValue(new_arr.text)
}
} else {
for (i=0; i<new_arr.length; i++) {
Test.getRange(ind+1,2+i).setValue(new_arr.text)
}
}
}
Здесь я сначала разделяю текст кнопки, которая была нажата, по пробелам, получая массив. Далее изменяю первый элемент (смайлик) на противоположное значение.
Объединяю все снова в один текст для этой кнопки и записываю в таблицу.
Сохраненная в таблице клавиатура будет выглядеть так
Кнопки клавы записываю в одну строку, чтобы не множить строки таблицы
Соответственно, если ботом пользуются несколько человек в разных чатах, то для каждого чата будет выделена строка в таблице под свою клавиатуру. Идентификатор является ид чата (первая колонка)
Также запилю функцию для отправки клавы пользователя в виде сообщения
function sendKeyboard(chat_id) { //вернет клаву в виде текста
let ind_keyboard = getInd(chat_id,Test);
let msg = Test.getRange(ind_keyboard+1,2,1,Test.getLastColumn()).getValues();
msg = msg.flat();
msg = msg.join(",\n");
return msg
}
С первой из них уже было знакомство в этой статье, вторую рассмотрим здесь.
function send_key (msg, chat_id, api, keyboard)
{
var payload = {
'method': 'sendMessage',
'chat_id': String(chat_id),
'text': msg,
'parse_mode': 'HTML',
reply_markup : JSON.stringify(keyboard)
}
var data = {
"method": "post",
"payload": payload
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + api + '/', data);
Функция отправляет инлайн-клавиатуру в чат вместе с сообщением. Соответственно на входе нам нужны текст сообщения, ид чата, куда мы эти сообщение и клаву отправляем, апи бота и сама клава.
Вызываем функции send() и send_key() из тела функции doPost().
Стандартная функция doPost() для коммуникации с ботом получает от нас одну из двух команд и выполняет два соотвутствующих этим командам действия:
{
let update = JSON.parse(e.postData.contents);
if (update.hasOwnProperty('message'))
{
let msg = update.message;
let chat_id = msg.chat.id;
let text = msg.text;
if (text == "/getkeyboard") {
let keyboardToSend = getKeyboard(chat_id);
Demo.send_key("Галочки", chat_id, API, keyboardToSend)
}
if (text == "/save") {
Demo.send("Клавиатура сохранена: \n" + sendKeyboard(chat_id), chat_id, API)
}
}
if (update.hasOwnProperty('callback_query')) {
let chat_id = update.callback_query.message.chat.id;
let vote = update.callback_query.data;
let msg_id = update.callback_query.message.message_id;
if (vote >= 0 && vote <= 11) {
setKeyboard(chat_id,vote);
Demo.send_key("Ваш выбор: ",chat_id,API,getKeyboard(chat_id));
}
}
}
В функции выше у нас имеются два условия
В переменную vote я записываю значение callback_data нажатой кнопки. callback_data мы указали в переменной FLAGS для каждой кнопки, и эти значения - числа от 0 до 10.
Эти же числа выполняют роль номера элемента массива при работе c кнопками в функции setKeyboard().
Сохраняем и деплоим
Создадим файл Api connector и запишем сюда функцию установки вебхука
function api_connector ()
{
let App_link = " ";
//App_link указываем свой и обновляем после каждого деплоя
UrlFetchApp.fetch("https://api.telegram.org/bot"+API+"/setWebHook?url="+App_link);
}
Укажем App_link (ссылка появляется в окне после деплоя) и запустим функцию api_connector.
Далее результат клика по одной из кнопок
Я бы еще удаляла предыдущую клаву, чтобы у нас оставалась только одна активная.
К списку функций в файл functions допишем следующую функцию
function del_inline(chat_id, msg_id) {
var payload = {
'method': 'editMessageReplyMarkup',
'chat_id': String(chat_id),
'message_id': String(msg_id)
}
var Data = {
"method": "post",
"payload": payload
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + API + '/', Data);
}
И добавим вызов этой функции в doPost()
Проверяем работу бота снова
Предыдущая клава успешна удалена.
По команде "/save" мы получаем от бота нашу клаву в виде сообщения
Недавно встал вопрос с множественным выбором. Бот предлагает пользователю список возможных вариантов, а пользователь в свою очередь имеет возможность выбрать несколько из этих вариантов. При том реализовано это через инлайн-клавиатуру
Ожидаемый результат работы флагов будет примерно следующий:
Краткая вводная дана, переходим к порядку реализации.
Создаем бота через @BotFather, создаем новую ГТ и открываем AppsScripts (подробнее про создание ботов можно почитать тут.
Начало
Как правило, я делю скрипт на несколько логических кусочков и сохраняю в разные файлыНастоятельно рекомендую поступать также.
В файл Global записываю глобальные переменные или константы.
const API = "Ваш АПИ";
const DOC = SpreadsheetApp.openById("Ваш ИД");
const Test = DOC.getSheetByName("Ваше название листа");
//в кавычки пропишите свои значения
Здесь
- API бота;
- ссылка на текущий гугл док;
- лист этого дока, с которым мы будем непосредственно работать.
Клавиатура и ее отправка в чат
В файле Keyboards создадим нашу клавиатуру. Клавиатуру я записываю в отдельную переменную (в данном случае FLAGS)Моя клава выглядит так:
let FLAGS =
{
"inline_keyboard": [
[{"text": " Уточнение деталей у клиента по вакансии устно", "callback_data": "0"}],
[{"text": " Уточнение деталей у клиента по вакансии письменно", "callback_data": "1"}],
[{"text": " Составление карты поиска", "callback_data": "2"}],
[{"text": " Размещение вакансий на различных источниках", "callback_data": "3"}],
[{"text": " Поиск на открытых источниках", "callback_data": "4"}],
[{"text": " Сорсинг", "callback_data": "5"}],
[{"text": " Телефонное интервью", "callback_data": "6"}],
[{"text": " Собеседование с видео по zoom/Skype", "callback_data": "7"}],
[{"text": " Технический скрининг на собеседование", "callback_data": "8"}],
[{"text": " Контроль выхода кандидата ", "callback_data": "9"}],
[{"text": " Контроль ИС", "callback_data": "10"}]
],
"resize_keyboard": true
};
Сейчас самое интересное…
Какая логика? Мы отправляем клаву в чат по запросу пользователя, по команде, например. Далее пользователь кликает на один из вариантов (кнопку), который хочет выбрать. Мы запоминаем его выбор и отправляем новую клавиатуру, где в тексте выбранной кнопки будет изменен флаг.
Внимательный читатель заметил, что в названиях кнопок есть смайлики. Они-то и будут нашими флагами, которые отражают одно из значений true/false, 1/0, да/нет.
Так как мы записываем клаву в таблицу, сначала будем проверять, есть ли уже эта клава в таблице и в какой именно ячейке она записана. Искать будем по ключу chat_id
function getInd(chat_id,sheet) { //возвращает индекс строки, в кот нах-ся ид
let lr = sheet.getLastRow();
let chat_id_arr = sheet.getRange(1,1,lr).getValues();
chat_id_arr = chat_id_arr.flat();
let ind = chat_id_arr.indexOf(chat_id);
return ind;
}
Функция выше возвращает id строки таблицы, в которой записан искомый ид чата. Id строки в таблице и номер строки - не одно и то же. Когда мы забираем все значения из таблицы и записываем их в массив (методом getValues()), значения в массиве начинаются с 0, тогда как в таблице отсчет ведется с 1. Просто помним об этом)
Следующая функция ищет chat_id в таблице и возвращает соответствующую этому ид клаву. В ином случае возвращает дефолтную клаву.
function getKeyboard(chat_id) { //забирает клаву из табл
let ind = getInd(chat_id,Test);
let lc = Test.getLastColumn();
let cur_keyboard = [];
let keys = [];
let KEYBOARD = {};
if (ind > 0) { //если ind=-1, chat_id не был найден в таблице
keys = Test.getRange(ind+1,2,1,lc).getValues();
keys = keys.flat();
for (i=0; i<keys.length; i++) {
cur_keyboard.push([{"text":keys, "callback_data":i}]);
}
if (cur_keyboard != "") {
KEYBOARD =
{
"inline_keyboard": cur_keyboard,
"resize_keyboard": true
}
} else {
KEYBOARD = FLAGS;
}
} else {
KEYBOARD = FLAGS;
}
return KEYBOARD
}
И разумеется, нужна функция для сохранения клавиатуры в таблице гугл
function setKeyboard(chat_id,vote) { //записывает текст клавы в табл
let ind = getInd(chat_id,Test);
let KEYBOARD = getKeyboard(chat_id);
let key = KEYBOARD.inline_keyboard[vote][0].text;
let flag = key.split(' ');
switch (flag[0])
{
case '' : flag.splice(0,1,''); break;
case '' : flag.splice(0,1,''); break;
}
flag = flag.join(' ');
KEYBOARD.inline_keyboard[vote][0].text = flag;
let new_arr = [];
new_arr = KEYBOARD.inline_keyboard.flat();
if (ind < 0) { //если ид нет в табл
let new_ind = Test.getLastRow()+1; //строка для записи нового ид
Test.getRange(new_ind,1).setValue(chat_id);
for (i=0; i<new_arr.length; i++) {
Test.getRange(new_ind,2+i).setValue(new_arr.text)
}
} else {
for (i=0; i<new_arr.length; i++) {
Test.getRange(ind+1,2+i).setValue(new_arr.text)
}
}
}
Здесь я сначала разделяю текст кнопки, которая была нажата, по пробелам, получая массив. Далее изменяю первый элемент (смайлик) на противоположное значение.
Объединяю все снова в один текст для этой кнопки и записываю в таблицу.
Сохраненная в таблице клавиатура будет выглядеть так
Соответственно, если ботом пользуются несколько человек в разных чатах, то для каждого чата будет выделена строка в таблице под свою клавиатуру. Идентификатор является ид чата (первая колонка)
Также запилю функцию для отправки клавы пользователя в виде сообщения
function sendKeyboard(chat_id) { //вернет клаву в виде текста
let ind_keyboard = getInd(chat_id,Test);
let msg = Test.getRange(ind_keyboard+1,2,1,Test.getLastColumn()).getValues();
msg = msg.flat();
msg = msg.join(",\n");
return msg
}
Функции отправки сообщений ботом
Бот будет общаться с пользователем с помощью двух функций send() и send_key().С первой из них уже было знакомство в этой статье, вторую рассмотрим здесь.
function send_key (msg, chat_id, api, keyboard)
{
var payload = {
'method': 'sendMessage',
'chat_id': String(chat_id),
'text': msg,
'parse_mode': 'HTML',
reply_markup : JSON.stringify(keyboard)
}
var data = {
"method": "post",
"payload": payload
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + api + '/', data);
Функция отправляет инлайн-клавиатуру в чат вместе с сообщением. Соответственно на входе нам нужны текст сообщения, ид чата, куда мы эти сообщение и клаву отправляем, апи бота и сама клава.
Вызываем функции send() и send_key() из тела функции doPost().
Стандартная функция doPost() для коммуникации с ботом получает от нас одну из двух команд и выполняет два соотвутствующих этим командам действия:
- отправляет клаву в чат;
- отправляет сообщение, текст которого содержит выбор пользователя
{
let update = JSON.parse(e.postData.contents);
if (update.hasOwnProperty('message'))
{
let msg = update.message;
let chat_id = msg.chat.id;
let text = msg.text;
if (text == "/getkeyboard") {
let keyboardToSend = getKeyboard(chat_id);
Demo.send_key("Галочки", chat_id, API, keyboardToSend)
}
if (text == "/save") {
Demo.send("Клавиатура сохранена: \n" + sendKeyboard(chat_id), chat_id, API)
}
}
if (update.hasOwnProperty('callback_query')) {
let chat_id = update.callback_query.message.chat.id;
let vote = update.callback_query.data;
let msg_id = update.callback_query.message.message_id;
if (vote >= 0 && vote <= 11) {
setKeyboard(chat_id,vote);
Demo.send_key("Ваш выбор: ",chat_id,API,getKeyboard(chat_id));
}
}
}
В функции выше у нас имеются два условия
- update.hasOwnProperty('message')
- update.hasOwnProperty('callback_query')
В переменную vote я записываю значение callback_data нажатой кнопки. callback_data мы указали в переменной FLAGS для каждой кнопки, и эти значения - числа от 0 до 10.
Эти же числа выполняют роль номера элемента массива при работе c кнопками в функции setKeyboard().
Сохраняем и деплоим
Создадим файл Api connector и запишем сюда функцию установки вебхука
function api_connector ()
{
let App_link = " ";
//App_link указываем свой и обновляем после каждого деплоя
UrlFetchApp.fetch("https://api.telegram.org/bot"+API+"/setWebHook?url="+App_link);
}
Укажем App_link (ссылка появляется в окне после деплоя) и запустим функцию api_connector.
Тестируем бота
Я отправила боту сообщение "/getkeyboard", на что он мне вернул клаву без галочекДалее результат клика по одной из кнопок
Я бы еще удаляла предыдущую клаву, чтобы у нас оставалась только одна активная.
К списку функций в файл functions допишем следующую функцию
function del_inline(chat_id, msg_id) {
var payload = {
'method': 'editMessageReplyMarkup',
'chat_id': String(chat_id),
'message_id': String(msg_id)
}
var Data = {
"method": "post",
"payload": payload
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + API + '/', Data);
}
И добавим вызов этой функции в doPost()
Проверяем работу бота снова
Предыдущая клава успешна удалена.
По команде "/save" мы получаем от бота нашу клаву в виде сообщения
Заключение
Я описала функционал, который может быть полезен в тех или иных ботах. Его конечно можно модифицировать или усовершенствовать и допилить под ваши нужды. Но, надеюсь, общая идея ясна.Множественный выбор кнопок в боте
В этом посте рассмотрим одну фичу, которая может быть полезна дорогому читателюНедавно встал вопрос с множественным выбором. Бот предлагает пользователю список возможных вариантов, а пользователь в...
habr.com