Множественный выбор кнопок в боте

от автора

В этом посте рассмотрим одну фичу, которая может быть полезна дорогому читателю

Недавно встал вопрос с множественным выбором. Бот предлагает пользователю список возможных вариантов, а пользователь в свою очередь имеет возможность выбрать несколько из этих вариантов. При том реализовано это через инлайн-клавиатуру

Ожидаемый результат работы флагов будет примерно следующий:

Клавиатура с двумя выбранными вариантами
Клавиатура с двумя выбранными вариантами

Краткая вводная дана, переходим к порядку реализации.

Создаем бота через @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[i], "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[i].text)     }   } else {       for (i=0; i<new_arr.length; i++) {       Test.getRange(ind+1,2+i).setValue(new_arr[i].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() для коммуникации с ботом получает от нас одну из двух команд и выполняет два соотвутствующих этим командам действия:

  • отправляет клаву в чат;

  • отправляет сообщение, текст которого содержит выбор пользователя

function doPost(e) {   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’)

В первый if мы попадаем при условии, что пользователь написал боту сообщение, а во второй, — когда пользователь нажал на нопку.

В переменную 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» мы получаем от бота нашу клаву в виде сообщения

Заключение

Я описала функционал, который может быть полезен в тех или иных ботах. Его конечно можно модифицировать или усовершенствовать и допилить под ваши нужды. Но, надеюсь, общая идея ясна.


ссылка на оригинал статьи https://habr.com/ru/articles/585546/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *