Продолжение статьи про использование гугл таблиц в качестве бд.
Краткое предисловие: в предыдущем посте я привела пример, как забирать текст из ячеек таблицы и отправлять этот текст в виде отдельных сообщений в телеграм-чат с ботом.
Как и обещала, в новом посте расскажу как забирать данные из ячеек и превратить их в кнопки, разместив их под сообщениями, которые мы успешно отправляли до этого.
Ячейки гт в кнопки тг - превращение
Вернемся к структуре таблицы, в которой лежат кнопки (к тем значениям, которые будут использованы для текста кнопок).

Это таблица Answers с тремя колонками:
ид текста вопроса, к которому относится ответ;
текст кнопки;
маркер верного ответа.
Дополним существующий скрипт обработкой данных из этой таблички и формированием объекта типа InlineKeyboardMarkup согласно документации Telegram bot api.
Вернемся к функции sendQuestions(chat_id) из последнего поста
function sendQuestions(chat_id) { const questionsArr = questionsSheet.getRange(1,1,questionsSheet.getLastRow(), questionsSheet.getLastColumn()).getValues(); Logger.log(questionsArr) questionsArr.forEach(e => send(e[1],chat_id)) }
И добавим строку для получения всех значений из вкладки Answers.
const answersArr = answersSheet.getDataRange().getValues();
Как было отмечено в комментариях к прошлой статье, есть более понятный способ получения всех данных из листа, а именно метод .getDataRange().
Предлагаю строку
const questionsArr = questionsSheet.getRange(1,1,questionsSheet.getLastRow(), questionsSheet.getLastColumn()).getValues();
тоже изменить на
const questionsArr = questionsSheet.getDataRange().getValues();
В итоге получаем
function sendQuestions(chat_id) { const questionsArr = questionsSheet.getDataRange().getValues(); const answersArr = answersSheet.getDataRange().getValues(); Logger.log(questionsArr) Logger.log(answersArr) //questionsArr.forEach(e => send(e[1],chat_id)) }
Последнюю строку в функции я пока закомментировала, так как взаимодействовать с чатом мы будем позже.
Запускаем функцию и получаем в логере два массива - с вопросами и ответами.

Приступаем к трансформации массива с вариантами ответов в объект keyboard.
На данном этапе я объявлю одноименную переме��ную, в которой укажу ключ inline_keyboard со значением массива из кнопок.
const keyboard = { "inline_keyboard": arr }
Значение arr - это двумерный массив. Вложенный массив включает в себя объект с описанием характеристик кнопки. Пример, как может выглядеть arr
const arr = [ [{"text":"button1", "otherKey1":"value1", "otherKey2":"value2"}], [{"text":"button2", "otherKey1":"value1", "otherKey2":"value2"}] ]
* Если в вашем проекте одна или несколько статичных клавиатур, вы можете задать их в виде переменных непосредственно в коде, например
const keyboard1 = { "inline_keyboard": [ [{"text": "buttonText1", "otherKey1":"value1", "otherKey2":"value2"}], [{"text": "buttontext2", "otherKey1":"value1", "otherKey2":"value2"}] ] } const keyboard2 = { "inline_keyboard": [ [{"text": "buttonText3", "otherKey1":"value1", "otherKey2":"value2"}], [{"text": "buttontext4", "otherKey1":"value1", "otherKey2":"value2"}], [{"text": "buttontext5", "otherKey1":"value1", "otherKey2":"value2"}] ] }
Ключ text обязательный и его значение выводится непосредственно в телеграме. Остальные доступные ключи можно посмотреть в документации, в этой задаче я дополнительно буду использовать только callback_data.
Давайте вспомним логику еще раз. Для каждого вопроса у меня есть несколько вариантов ответа, соответственно на одно сообщение мне нужно сформировать список из всех доступных на этот вопрос ответов по ид этого вопроса.
Вернемся к обработке массива с вопросами в строке questionsArr.forEach(e => send(e[1],chat_id)). Для этого перепишу ее немного иначе
questionsArr.forEach(e => { //send(e[1],chat_id) })
В теле стрелочной функции объявим наш arr для объекта клавиатуры и сам объект keyboard
questionsArr.forEach(e => { let arr = answersArr.filter(el => el[0] == e[0]) const keyboard = { "inline_keyboard": arr } //send(e[1],chat_id) })
В новой добавленной строке кода я объявляю переменную массива, в нее записываю значения из массива с ответами, которые предварительно фильтрую по признаку, что ид вопроса из таблицы Questions равно ид вопроса из таблицы Answers. Использую, соответственно, метод массива filter().
Внутри метода я ссылаюсь на элемент массива answersArr используя обозначение el, и сравниваю значение el на позиции 0 с элементом массива questionsArr также на позиции 0.
Результат фильтрации выведу в логере
questionsArr.forEach(e => { let arr = answersArr.filter(el => el[0] == e[0]) Logger.log(arr) const keyboard = { "inline_keyboard": arr } //send(e[1],chat_id) })

*Такой же результат можно получить вложенными циклами
for (let i=0; i<questionsArr.length; i++) { let arr = new Array(); for (let j=0; j<answersArr.length; j++) { if (questionsArr[i][0] == answersArr[j][0]) arr.push(answersArr[j]) } Logger.log(arr) }
Начиная с третьей строки в логере, выводятся отфильтрованные значения по каждому из ид вопросов - 1, 2, 3 и 4.
Теперь мне нужно модифицировать каждый такой массив, для чего воспользуюсь методом .map().
questionsArr.forEach(e => { let arr = answersArr.filter(el => el[0] == e[0]) arr = arr.map(el => [{"text":el[1], "callback_data":el[1]}]) Logger.log(arr) const keyboard = { "inline_keyboard": arr } //send(e[1],chat_id) })
Внутри метода map() ссылаюсь на элемент массива arr через el, беру значение на позиции 1 и присваиваю его ключам text и callback_data. То есть элемент [1.0, Джейк пес, ] превратится в [{"text":"Джейк пес", "callback_data":"Джейк пес"}]
И пример того, как это будет в циклах без применения методов (ну за исключением одного)
for (let i=0; i<questionsArr.length; i++) { let arr = new Array(); for (let j=0; j<answersArr.length; j++) { if (questionsArr[i][0] == answersArr[j][0]) { const val = answersArr[j][1] const button = {"text":val, "callback_data":val} arr.push([button]) } } Logger.log(arr) }
*Метод .push() вставляет указанный в скобках элемент в конец массива
И вывод в логере модифицированного массива arr

Вся функция sendQuestions() ниже
function sendQuestions() { const questionsArr = questionsSheet.getDataRange().getValues(); const answersArr = answersSheet.getDataRange().getValues(); Logger.log(questionsArr) Logger.log(answersArr) //Альтернатива 1 /* for (let i=0; i<questionsArr.length; i++) { let arr = new Array(); for (let j=0; j<answersArr.length; j++) { if (questionsArr[i][0] == answersArr[j][0]) { const val = answersArr[j][1] const button = {"text":val, "callback_data":val} arr.push([button]) } } Logger.log(arr) } */ //конец Альтернативы 1 //Альтернатива 2 questionsArr.forEach(e => { let arr = answersArr.filter(el => el[0] == e[0]) arr = arr.map(el => [{"text":el[1], "callback_data":el[1]}]) Logger.log(arr) //конец Альтернативы 2 const keyboard = { "inline_keyboard": arr } //send(e[1],chat_id) }) }
На этом этапе мы готовы отправить клавиатуру с сообщением, но нам нужна функция, которая эта делает. Внесем некоторые правки в существующую функцию send().
У нас появился новый передаваемый параметр, имя ключа этого параметра reply_markup, а значение keyboard:
'reply_markup' : JSON.stringify(keyboard)
Не забудем указать keyboard в параметрах функции. Таким образом, получим
function send(msg, chat_id, keyboard) { const payload = { 'method': 'sendMessage', 'chat_id': String(chat_id), 'text': msg, 'parse_mode': 'HTML', 'reply_markup' : JSON.stringify(keyboard) } const data = { 'method': 'post', 'payload': payload, 'muteHttpExceptions': true } UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data); }
Сохраняем проект и деплоим (Как это делать?). Не забываем запустить функцию api_connector() с новым значением appLink
function api_connector() { const appLink = "Ваш URL"; UrlFetchApp.fetch("https://api.telegram.org/bot"+token+"/setWebHook?url="+ appLink); }
Идем в чат, отправляем любой текст и… видим результат!

Бот ответил отправив все вопросы с кнопками вариантов ответов.
Ниже весь код, который у нас есть на текущий момент
const doc = SpreadsheetApp.getActive(); const questionsSheet = doc.getSheetByName("Questions"); const answersSheet = doc.getSheetByName("Answers"); const usersSheet = doc.getSheetByName("Users"); const token = "Ваш токен" function api_connector () { const App_link = "Ваш URL"; UrlFetchApp.fetch("https://api.telegram.org/bot"+token+"/setWebHook?url="+App_link); } function doPost(e) { const update = JSON.parse(e.postData.contents); let msgData = {} if (update.hasOwnProperty('message')) { msgData = { id : update.message.message_id, chat_id : update.message.chat.id, user_name : update.message.from.username, text : update.message.text, is_msg : true }; } sendQuestions(msgData.chat_id); } function send(msg, chat_id, keyboard) { const payload = { 'method': 'sendMessage', 'chat_id': String(chat_id), 'text': msg, 'parse_mode': 'HTML', 'reply_markup' : JSON.stringify(keyboard) } const data = { 'method': 'post', 'payload': payload, 'muteHttpExceptions': true } UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data); } function sendQuestions(chat_id) { const questionsArr = questionsSheet.getDataRange().getValues(); const answersArr = answersSheet.getDataRange().getValues(); Logger.log(questionsArr) Logger.log(answersArr) questionsArr.forEach(e => { let arr = answersArr.filter(el => el[0] == e[0]) arr = arr.map(el => [{"text":el[1], "callback_data":el[1]}]) Logger.log(arr) const keyboard = { "inline_keyboard": arr } send(e[1], chat_id, keyboard) }) }
Продолжение следует...
Следующим шагом, как не сложно догадаться, будет обработка ответов от пользователей бота с сохранением их в таблицу.
Спасибо за комментарии к прошлому посту и обязательно дайте знать, если что-то непонятно, буду рада помочь :)