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

Краткая вводная дана, переходим к порядку реализации.
Создаем бота через @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" мы получаем от бота нашу клаву в виде сообщения

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