Всем привет! Спасибо за интерес к предыдущим постам (раз и два), а сегодня продолжаем дополнять нашего бота функционалом. Сохраним ответы из телеграма от пользователей в гугл таблицу. Сначала я бы хотела сделать пол шага назад и показать, в каком виде мы получаем информацию из телеграма, когда мы кликаем по кнопке, отправляем сообщение боту или любое другое действие. Любая активность пользователя возвращается в виде json пакета. Например, мое сообщение /start отправляет  HTTP POST запрос к веб-приложению (веб-приложение это наш скрипт). Скрипт, в свою очередь, с помощью Simple trigger doPost(e) вернет пакет как в снипете ниже. e в doPost(e) означает event. Далее обращаемся к документации и изучаем методы для event. Находим e.postData.contents и используем его для парсинга json-пакета. Функция парсинга у нас уже есть: В функции я объявляю переменную update, в которую записываю содержание пакета. Далее объявляю объект msgData, проверяю есть ли у переменной update свойство 'message' и записываю в msgData нужные мне значения. Помимо свойства message могут быть callback_data, edited_message, forwarded_message и так далее. При этом возвращаемый пакет будет содержать разные ключи и вложенность и, соответственно, в парсинге необходимо предусматривать все кейсы, которые нас интересуют. У нас есть кейс с кнопками, нажатие на какую-либо из них возвращает пакет вида Очевидно, что пакеты различаются и порядок парсинга тоже изменится. Дополним функцию doPost(e) обработкой нажатия кнопки: Сам объект msgData можно дополнять любыми необходимыми вам значениями. В моем примере, помимо некоторых данных из пакета, я добавила идентификаторы is_msg и is_button. Надеюсь, что в этой части я привела достаточные вводные как работать с пакетами и парсингом. Функцию doPost(e) я оставлю только для парсинга и вызову в ней в последней строке новую функцию dataHandler(msgData). Обозначим ее пока приблизительно, проверив какое событие (event) вернулось — сообщение или кнопка. Если бот получил сообщение, отправляем вопросы, если кнопку, сохраняем ответы. Структура таблицы для сохранения ответов следующая: Функция сохранения ответов saveData(msgData): В переменную vals я записываю сохраняемые данные в том порядке, в котором они будут выведены в таблице. На лист usersSheet вставляю новую строку с помощью метода appendRow() и указанием вставляемого массива. Результат выполнения функции приведен на скрине выше во второй строке таблицы. Далее будем проверять правильность ответов и возвращать пользователю результат. Здесь нам нужно сопоставить сообщение с вопросом из таблицы, затем вопрос из таблицы - с ответами из таблицы Answers и проверить выбранный из 4 вариантов ответ на корректность.  Здесь я бы хотела использовать не текст вопроса, а его ид. Поменяю текст вопроса так, что в начале вопроса будет его ид. В функции sendQuestions(chat_id) вместо строки send(e[1], chat_id, keyboard)напишу две следующие: Таким образом, в объекте msgData после нажатия кнопки мы можем спарсить ид вопроса и записать его в таблицу с ответами, изменив функцию saveData(msgData): В данном случае, из текста сообщения мы сохраняем только первый символ, что соответствует ид вопроса. Как это выглядит в таблице. *Если дата записана в виде магических чисел, измените формат ячеек в соответствующей колонке. Теперь добавим интерактивности нашему боту и дадим пользователю понять, верный ли он дал ответ или нет. Пример ниже После ответа бот показывает правильность ответов в тексте кнопок, а в тексте сообщения выводит ответ пользователя и корректность данного ответа. Для начала обозначим функцию, которая редактирует уже отправленное сообщение: Функция использует метод editMessageText, обязательными параметрами являются text, chat_id и message_id. Дополнительно мы можем указать другие параметры, в том числе клавиатуру. *Чтобы не запутаться, храните функции в разных файлах, разделив их по предназначению. Например, функции для отправки данных в телеграм, глобальные переменные и функция парсинга расположены у меня в разных файлах. Далее создам еще одну функцию editMessage(msgData), в которой нам нужно сравнить ответ пользователя с вариантами ответов из таблицы, и в зависимости от правильности ответа внести соответствующие правки в текст. Начнем с ответов из таблицы, записав их в переменную answersArr, что мы уже делали в функции отправки вопросов. Воспользуюсь методом массива .filter(), оставив варианты ответа только для текущего вопроса: В данном случае я сравниваю первый элемент массива answersArr с первым символом в строке текста сообщения, то есть сопоставляю ид вопроса из таблицы с ответами с ид вопроса из текста сообщения. Сохраним в переменную curAnswerArr строку из таблицы с ответами, которая соответствует данному пользователем ответу: Здесь в уже отфильтрованном массиве из 4х вариантов ответов я выбираю один, текст которого равен тексту в кнопке. Далее отредактирую сам текст сообщения в зависимости от того, правильный был ответ или нет: Если данный пользователем ответ имеет плашку TRUE, то в текст добавляю галочку, или крестик в обратном случае. И наконец прописываем строку с вызовом функции editMsg() и передаем новый текст сообщения, ид чата, ид сообщения и null для клавиатуры. Вся функция saveData(msgData) представлена ниже: На этом этапе деплоим и проверяем, что все работает: Клавиатура под сообщением пропала, т.к. в editMsg вместо клавиатуры мы передали null. Редактирование клавиатуры оставлю читателю для самостоятельного изучения. Может один из моих старых постов поможет 😉  Перейдем к вопросам в комментах: как отправлять вопросы последовательно (следующий вопрос после ответа на предыдущий); как отправить свой ответ. Вопросы, кстати, взаимосвязаны, и добавление любой из логик сопровождается тем, что мы должны отслеживать прогресс прохождения квиза. Го! *Я не буду погружаться в детали, как делала до этого, иначе чтиво затянется на часы. Прошу понять и принять, что эта часть скорее про дополнительные фичи. До сих пор мы записывали все клики в таблицу, и моя таблица после нескольких ответов выглядит так: Сделаем так, чтобы каждое прохождение квиза по-прежнему отражалось в таблице, но каждая попытка конкретного пользователя была в одной строке. Тогда можно вынести ид вопросов в отдельные колонки. Изменю таблицу Users так, чтобы колонка C стала колонкой progress, что отражает на каком этапе квиза данный пользователь. Названия колонок E-H соответствуют ид вопросов. *Используйте функцию Transpose для быстрого добавления названий колонок. Вернемся к функции отправки вопросов, скопируем ее и переименуем копию в sendQuestion, оригинальная функция вам еще может пригодиться. Также я вынесу формирование клавиатуры в отдельную функцию createKeyboard() и получу следующее: Определим, какой именно вопрос является следующим для пользователя на основании его текущей попытки. Передадим nextState (пока обозначим переменную условно) в функцию sendQuestion(msgData, nextState) и проверим последний ли это вопрос из всех возможных или нет Если вопрос был последним, отправляем поздравительное сообщение. Разбираемся с этим nextState. Так как все ид просто увеличиваются на 1, то следующий вопрос это последний отправленный ид + 1. А текущее состояние или progress, нужно забрать из таблицы, если пользователь нажал на кнопку. Добавлю функцию getRow(chat_id), которая по чат ид находит последнюю попытку на вкладке usersSheet. Я решила создать объект Map и внести туда данные о пользователе в виде ключ - значение, что также позволяет сохранить определенную последовательность этих пар. В rowMap я внесу все ключи и присвою им соответствующие значения. Также нам нужно просматривать массив всех пользователей не с начала, а с конца, т.к. самый "свежий" старт квиза записывается в конец таблицы, поэтому воспользуюсь методом массива .reverse(). headerUsersArr это шапка таблицы, то есть названия колонок. В rowMap при этом запишутся значения в условном виде: {username="brainwashed_from_rock", progress=1,....}. Теперь из этого объекта можно достать текущий прогресс: Функция для обновления строки в таблице. Я записываю юзернейм (т.к. может менятся), ответ на текущий вопрос, ид следующего вопроса, дату. И наконец записываю в нужную строку измененный и трансформированный в массив объект Map. Теперь изменим dataHandler(msgData) под новую логику: Бот будет реагировать только на сообщения с командой /start или кнопки. Если это старт, то создаем новую строку с данными пользователя и отправляем первый вопрос. И если кнопка, редактируем текст сообщения, забираем актуальную строку из таблицы Users, определяем следующий вопрос для пользователя и отправляем его. И конечно сохраняем ответ в таблицу. Теперь мы можем добавить возможность давать свободный ответ. Пример чата и таблицы ниже. Изменим setChatsVals(msgData, progress, rowMap, flag): В первую строку я добавила проверку флага на is_button. Если да, записываем в таблицу значение кнопки, если нет, то текст сообщения. И немного отредактируем dataHandler(msgData): Здесь в условии is_msg  я прописала исключение и включила туда ту же самую логику, как и для кнопки. На этом все! Основная задача, которую я ставила в самом первом посте, а именно "Гугл таблица как база данных", на мой взгляд, выполнена. Мы можем перекидавать данные из гугл таблицы в телеграм чат и из чата в гугл таблицу. Доработать можно еще много чего на ваш вкус и цвет, внести всякие проверки и прочее. Ссылка на мой бот, для прохождения квиза. Через некоторое время выложу весь код с редактированием кнопок. UPD: ссылка на код