Гугл таблица как БД для телеграм бота (продолжение)

от автора

Продолжение статьи про использование гугл таблиц в качестве бд.

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

Как и обещала, в новом посте расскажу как забирать данные из ячеек и превратить их в кнопки, разместив их под сообщениями, которые мы успешно отправляли до этого.

Ячейки гт в кнопки тг — превращение

Вернемся к структуре таблицы, в которой лежат кнопки (к тем значениям, которые будут использованы для текста кнопок).

Таблица Answers

Таблица Answers

Это таблица 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)   }) }

Продолжение следует…

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

Спасибо за комментарии к прошлому посту и обязательно дайте знать, если что-то непонятно, буду рада помочь 🙂

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

Вы еще здесь?.

75% ✔️ да, давай еще27
16.67% ? ничего не понятно, но очень интересно6
8.33% ? мимо проходил(а)3

Проголосовали 36 пользователей. Воздержались 4 пользователя.

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