Пишем телеграм-бота. Часть 3: Обработка того, что приходит от бота и отправка информации боту

Часть 1: где рассматривается «серверная» часть, находящаяся на стороне гугл таблиц

Часть 2: начало работы с ботом, вебхуки, doPost

Итак, мне удалось через doPost получить данные. Теперь их надо немного изменить для последующей обработки и передавать дальше.

doPost(e)

// основная функция, которая обрабатывает входящие данные от телеграма
function doPost(e){

  const main = JSON.parse(e.postData.contents); // получаем данные из телеграм бота
 
  // вытаскиваем все данные из contents
  const chatId = main.message.chat.id;
  const firstName = main.message.chat.first_name;
  const lastName = main.message.chat.last_name;
  const messageText = main.message.text;
  const arrayText = messageText.split(" "); // массив для последующей обработки команд
  
  const detailsArray = [chatId, firstName, lastName, messageText, arrayText]; //собираем все что выше массив
  const objToTransfer = Object.assign({}, detailsArray); // делаем из массива объект для передачи

  analyseDetails(objToTransfer); //вызываем функцию для дальнейшей обработки того, что пришло
      
}

Первую часть с JSON я описывал ранее.

Далее я беру по ключу объекта его значение и самое важное тут chatId = main.message.chat.id;

Это уникальный айди пользователя, общающегося с ботом.

Далее я собираю все это в массив, на основе которого создаю объект при помощи Object.assign({}, detailsArray)

В чем прелесть Object.assign так это то, что созданный объект имеет вид: {0: chatId, 1: firstName, 2: lastName}.

Далее созданный объект я передаю в функцию analyseDetails

analyseDetails(obj)

function analyseDetails(objReceived){ // анализ того, что пришло со стороны телеграма
 
  const receivedCommand = objReceived[4][0].toLowerCase(); // "4": ["/list", "", "" и тд]

  switch(receivedCommand){ //если пришла команда
    case '/start':
      sendStartMessage(objReceived);
      break;

    case '/help':
      sendStartMessage(objReceived);
      break;
        
    case '/join':
      setNewUser(objReceived);
      break;

    case '/list':
      sendList(objReceived);
      break;

    default: // если пришло что-то отличное от команды
      onModer(objReceived);
      break;
  }
}

Функция берет пятое ([4]) значение объекта — текстовый массив arrayText и смотрит на содержание его первого ([0]) элемента. Если там какая-либо команда то происходит выполнение функции по имени этой команды, если там текст — отправка его на модерацию администратору телеграм-бота

Case /start и /help

function sendStartMessage(obj){ 
  
  const userId = obj[0]; //Юзер id
  const startMessage = `🤖 Привет! 🤖\n\n В своей базе  я храню данные по срокам предоставления налоговых и бухгалтерских отчетов и деклараций. За ${LESSDAYS} дней до окончания срока я высылаю уведомление об этом.\n\n Чтобы я смог присылать такие уведомления Вам, отправьте команду /join\n\n Чтобы получить список деклараций и сроков их подачи в ближайший квартал, отправьте команду /list\n\n Если Вам необходимо добавить в базу недостающие сроки подачи деклараций, отправьте мне сообщение в виде "Дата: Декларация" и я передам его на модерацию`

  sendMessage(userId, startMessage);
}

Obj — это тот объект, что приходит из doPost в analyseDetails и далее из него идет на все команды

sendMessage — основная функция, передающая что-либо в телеграм

sendMessage(chatid, chatText)

Вся информация по методу детально описана здесь — https://core.telegram.org/bots/api#sendmessage и повторять ее нет никакого смысла

// базовая функция для отправки сообщения в телеграм с "сервера"
function sendMessage(chatid, chatText) {
 
  const chatId = chatid;
  const text = chatText;

  const payload = {
      "method": "sendMessage",
      "chat_id": String(chatId),
      "text": text,
      "parse_mode": 'HTML',
      "disable_web_page_preview": true
  };

  const data = {
       "method": "post",
      "payload": payload
  };

  UrlFetchApp.fetch(telegramUrl, data);
}

UrlFetchApp.fetch(telegramUrl, data); — передает данные со стороны гугл таблиц на телеграм

Case /join — setNewUser(obj)

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

function setNewUser(obj){
  const userId = obj[0]; //Юзер id
 
  const userArr = wsUsers.getDataRange().getValues() // получаю массив массивов c пользовательскими id
  const flatUserArr = userArr.flat() // делаю его обычным массивом для последующего indexOf
  const userIsHere = flatUserArr.indexOf(userId) // делаю проверку наличия записи пользовательского ID

  if(userIsHere !== -1){ // если такой пользователь уже есть 
    sendMessage(userId, "Пользователь уже есть в моей базе 🧐")
  } else { // добавляю нового пользователя
    wsUsers.appendRow([userId]) // appendRow - каждая ячейка это массив
    sendMessage(userId, "Пользователь был успешно добавлен.\nВ ближайшее время ждите рассылку!")
  }
  
}

Функция setNewUser() проверяет наличие записанного пользовательского id на листе «Users» и если такого айди там нет — записывает его через wsUsers.appendRow([userId])

Case /list — отправка списка деклараций к подаче в ближайшие 90 дней

// для выполнения /list - список подаваемых деклараций на ближайшие QUARTER дней
function getQuarterList(){
    
  const today = new Date() // сегодня

  const quarter = new Date()
  quarter.setDate(today.getDate() + QUARTER) // сегодня + QUARTER дней

  const allArr = wsDb.getDataRange().getValues() // массив со всеми датами и декларациями

  const filteredArr = allArr.filter(row => (new Date(row[0])) > today && (new Date(row[0])) < quarter) // выбираем записи которые больше чем сегодня и меньше чем через QUARTER дней
  const beautyArr = filteredArr.map(row => [(row[0].toLocaleString().split(',')[0]) + " : " + row[1]]) // причесываем для последующей конвертации в текст
  
  const arrToText = beautyArr.join('\n')
  
  return arrToText
}

// послать список подаваемых деклараций на ближайшие QUARTER дней
function sendList(obj){
 const userId = obj[0]; //Юзер id
 const text =  getQuarterList() // данные к подаче на ближайшие QUARTER дней

 sendMessage(userId, text);
}

getQuarterList — выбирает весь список данных и фильтрует их между датами сегодня и сегодня + 90 дней

sendList — посылает список пользователю

Case default — когда приходит что угодно

// если пришло что-то отличное от команды - отправить администратору бота
function onModer(obj){
  const userId = obj[0]
  const userName = obj[1]
  const userSurname = obj[2]
  const userMessage = obj[3]

  const text = `Пользователь ${userName} ${userSurname} с ID ${userId} послал на модерацию сообщение: ${userMessage}`
  
  sendMessage(ADMIN_ID, text)
}

Функция отправляет «что угодно, отличное от пришедших команд» администратору ADMIN_ID на модерацию

И последняя часть — «серверная»:

Отправка боту «списка приближающихся сроков» по триггеру

// Фильтрованный массив с датами в текст
 function filteredArrToText(){
   
  const filteredArr = filteredDaysArr(); // фильтрованный массив
  const beautyArr = filteredArr.map(row =>[row[0]+" : " +row[1]]) // "причесанный" фильтрованный массив
  
  const textFromArr = beautyArr.join('\n') // массив в текст с разделителем новой строки для отправки в телеграм
  
  return textFromArr
 
 }

 // Для ежедневного триггера - проверка на наличие отправки
 function checkerToSend(){
   const text = filteredArrToText() // что сегодня рассылать - текст из фильтрованного массива
   
   if(text.length !== 0){ // если длина текста не равна 0 - т.е. там есть что отправлять
     const userArr = wsUsers.getDataRange().getValues() // получаю массив с пользовательскими id - кому идет рассылка
     
     for(let elem of userArr){ // для каждого элемента массива с пользовательскими id
       let messageText = `📨  Через ${LESSDAYS} дней срок подачи:\n`
       sendMessage(elem, (messageText + text)) // отправка сообшения
     }
   }
 }

checkerToSend вешается на триггер по дням и если длина text собранного из filteredArrToText не равна 0, т.е. когда там есть текст — отправляется на всех пользователей из wsUsers.getDataRange().getValues()

На этом написание телеграм-бота можно считать законченным. Останется только навести красоту через @BotFather с командами.


Ваше мнение важно и может улучшить блог

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

У этой записи 9 комментариев

  1. Егор Караваев

    Тоже делаю что-то подобное с ботом. Передачу любого текста из таблицы боту и обратно освоил, но никак не могу настроить передачу картинок через sendPhoto.
    Допустим, хочу отправлять боту pie chart в виде картинки. Приходится сохранять сначала на google drive, а оттуда уже по URL отправлять в телегу. Но проблема в том, что URL меняется и в итоге отправляется один и тот же файл. При этом с отправкой картинки на gmail все работает как надо. Были похожие задачи?

    function createPieChart(){

    var parents = DriveApp.getFileById(«1tQzMwEtPeFLx….6SfJ_BMcbb4-Y7FQY»).getParents();
    var folder = parents.hasNext() ? parents.next() : DriveApp.getRootFolder();

    var sheet = SpreadsheetApp.openById(«1tQzMwEtPeFLxMx….bH6SfJ_BMcbb4-Y7FQY»).getSheetByName(«Settings»);
    var totalChartLabels = sheet.getRange(«A2:A28»);
    var totalChartValues = sheet.getRange(«D2:D28»);

    var totalsChart = sheet.newChart()
    .setChartType(Charts.ChartType.PIE)
    .addRange(totalChartLabels)
    .addRange(totalChartValues)
    .setMergeStrategy(Charts.ChartMergeStrategy.MERGE_COLUMNS)
    .setPosition(6,7,0,0)
    .setOption(‘legend.position’, ‘bottom’)
    .setOption(‘pieSliceText’, ‘value-and-percentage’)
    .setOption(‘width’, 300)
    .setOption(‘height’, 300)
    .setNumHeaders(1)

    var blob = totalsChart.build().getBlob();

    //Email
    //sendMail(blob);

    //Google Drive
    folder.createFile(blob);

    //Telegram
    sendChart(adminID);
    }

    function sendChart(id){
    var url = telegramUrl + «/sendPhoto?chat_id=» + id + «&photo=» + «https://drive.google.com/file/d/1avX11sxnpJ8…KKq34qM-oHcryf/view?usp=sharing»;
    var response = UrlFetchApp.fetch(url);
    }

    1. Добрый день!
      Вам в коде нужно дать доступ к файлу другим системам (а не только вам) и еще получить урл:
      const resultURL = resultFile.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW).getUrl()

      function getPicture() {

      const chart = ws.newChart()
      .asColumnChart()
      .addRange(ws.getRange('A1:B7'))
      .setMergeStrategy(Charts.ChartMergeStrategy.MERGE_COLUMNS)
      .setTransposeRowsAndColumns(false)
      .setNumHeaders(-1)
      .setHiddenDimensionStrategy(Charts.ChartHiddenDimensionStrategy.IGNORE_BOTH)
      .setPosition(9, 1, 448, 1)
      .build();

      const chartBlob = chart.getBlob()
      const resultFile = DriveApp.createFile(chartBlob)
      const resultURL = resultFile.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW).getUrl()

      console.log(resultURL)
      }

  2. Григорий

    Здравствуйте. Очень интересно. Подскажите, пожалуйста, обычному пользователю, где можно найти следующего бота?
    1. Пользователь заполняет свой номер телеграм и имя и артикул товара.
    2. Пользователю приходит стоимость товара и полное название по артикулу из Гугл таблиц, которые можно дополнять. http://www.ph.bezrukov@gmail.com

    1. Добрый день.
      1. Заполнять свой номер и имя не надо, чат бот это сам присылает.
      2. В данном случае гугл таблицы выполняют функцию БД и серверной части. У вас уже есть такая таблица?

      В свое время писал бота по проверке и отслеживанию цены по артикулу товара на вайлдбериз @WBCheckBot — посмотрите на его функционал, если схож, могу подсказать как написать аналогичного бота.

  3. Евгений

    Дмитрий, полезная статья, благодарю!
    Подскажи, плиз, а при рассылке сообщений не нужно учитывать лимиты телеграм на отправку 30 сообщений в 1 сек? Если, например, пользователей в массиве на отправку будет 300 человек.
    В коде только цикл.
    for(let elem of userArr){ // для каждого элемента массива с пользовательскими id
    let messageText = `📨 Через ${LESSDAYS} дней срок подачи:\n`
    sendMessage(elem, (messageText + text)) // отправка сообшения
    }

    Или тут как-то по-другому работает? Если не сложно, поясни плиз ;)))

    1. Привет.
      Спасибо за очень хороший вопрос.
      Учитывать конечно нужно. Равно как и учитывать квоты самого гугла (я об этом тоже уже много раз писал) — 300, кстати, гугл пропустит.
      Тут 2 пути:
      1) Гугловый — использовать Utilities.sleep() — когда скрипт засыпает на указанное количество миллисекунд. Немедленно переводит скрипт в спящий режим на указанное количество миллисекунд. Максимально допустимое значение — 300 000 (или 5 минут). То есть можно принудительно ограничить количество отправок за определенный интервал времени
      Можно сделать массив рассылки сообщений пользователей и через каждые 30 сообщений — засыпать.
      2) Телеграмовый — просто создать группу в которой находятся хоть 300 хоть 3000 пользователей и туда завесить бота. Бот будет слать всего лишь одно сообщение в группу (id которой начинается со знака «-«) и оно будет видно всем пользователям.

      В целом гугл скрипт в связке с таблицами (или firebase) по работе с телегой — далеко не самое лучшее решение, когда количество пользователей начинает расти по экспоненте. Но он бесплатный, остальные же варианты — стоят денег. Именно поэтому я использую второй путь, через группы телеграма.

      1. Евгений

        Дмитрий, спасибо за такое подробное пояснение!
        Да, про второй вариант тоже думал, что можно так делать. Именно интересовал вариант «Гугловый » :)))
        А так да, гугл скрипт + телеграм бот — есть понимание, что для очень большого количества пользователей не гууд. Но для себя и небольшой базы, думаю самое то:)

        Еще такой вопрос возник: вот например я использую 1 вариант и ставлю выполнение скрипта на паузу, всё это зацикливаю. Отправилась порция, пауза, отправилась порция, пауза и т.д. Как по лимитам гугла это будет? Пока скрипт «спит» — не идет серверное время? Время исполнения считается? Или как?
        Вопрос к тому, что, например, есть база в 3 000 чел. настроил на отправка 30 / пауза. Это 100 пауз. Не будет такого, что на 25 или 45 цикле выполнение остановится и все, дальше не пойдет?

        1. Тут только тестировать «нагрузку» при квотах — https://developers.google.com/apps-script/guides/services/quotas?hl=ru
          Я бы, наверное, создал условно 3000 записей «самого себя» и начал пробовать отправку одного сообщения себе эти все 3000 раз, заодно бы указывая номер элемента массива в сообщении, чтоб если что понять в какой момент сломалась отправка.

          1. Евгений

            Ок, благодарю за совет) Нужно провести эксперимент 🙂

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