Часть 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 с командами.
Ваше мнение важно и может улучшить блог
Я хочу услышать ваше мнение и ваши идеи о том, как сделать этот сайт еще лучше. Примите участие в опросе, чтобы поделиться вашими пожеланиями, предложениями и замечаниями. Пройдите опрос сейчас и помогите сделать этот сайт более полезным для вас!
Тоже делаю что-то подобное с ботом. Передачу любого текста из таблицы боту и обратно освоил, но никак не могу настроить передачу картинок через 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);
}
Добрый день!
Вам в коде нужно дать доступ к файлу другим системам (а не только вам) и еще получить урл:
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)
}
Здравствуйте. Очень интересно. Подскажите, пожалуйста, обычному пользователю, где можно найти следующего бота?
1. Пользователь заполняет свой номер телеграм и имя и артикул товара.
2. Пользователю приходит стоимость товара и полное название по артикулу из Гугл таблиц, которые можно дополнять. http://www.ph.bezrukov@gmail.com
Добрый день.
1. Заполнять свой номер и имя не надо, чат бот это сам присылает.
2. В данном случае гугл таблицы выполняют функцию БД и серверной части. У вас уже есть такая таблица?
В свое время писал бота по проверке и отслеживанию цены по артикулу товара на вайлдбериз @WBCheckBot — посмотрите на его функционал, если схож, могу подсказать как написать аналогичного бота.
Дмитрий, полезная статья, благодарю!
Подскажи, плиз, а при рассылке сообщений не нужно учитывать лимиты телеграм на отправку 30 сообщений в 1 сек? Если, например, пользователей в массиве на отправку будет 300 человек.
В коде только цикл.
for(let elem of userArr){ // для каждого элемента массива с пользовательскими id
let messageText = `📨 Через ${LESSDAYS} дней срок подачи:\n`
sendMessage(elem, (messageText + text)) // отправка сообшения
}
Или тут как-то по-другому работает? Если не сложно, поясни плиз ;)))
Привет.
Спасибо за очень хороший вопрос.
Учитывать конечно нужно. Равно как и учитывать квоты самого гугла (я об этом тоже уже много раз писал) — 300, кстати, гугл пропустит.
Тут 2 пути:
1) Гугловый — использовать Utilities.sleep() — когда скрипт засыпает на указанное количество миллисекунд. Немедленно переводит скрипт в спящий режим на указанное количество миллисекунд. Максимально допустимое значение — 300 000 (или 5 минут). То есть можно принудительно ограничить количество отправок за определенный интервал времени
Можно сделать массив рассылки сообщений пользователей и через каждые 30 сообщений — засыпать.
2) Телеграмовый — просто создать группу в которой находятся хоть 300 хоть 3000 пользователей и туда завесить бота. Бот будет слать всего лишь одно сообщение в группу (id которой начинается со знака «-«) и оно будет видно всем пользователям.
В целом гугл скрипт в связке с таблицами (или firebase) по работе с телегой — далеко не самое лучшее решение, когда количество пользователей начинает расти по экспоненте. Но он бесплатный, остальные же варианты — стоят денег. Именно поэтому я использую второй путь, через группы телеграма.
Дмитрий, спасибо за такое подробное пояснение!
Да, про второй вариант тоже думал, что можно так делать. Именно интересовал вариант «Гугловый » :)))
А так да, гугл скрипт + телеграм бот — есть понимание, что для очень большого количества пользователей не гууд. Но для себя и небольшой базы, думаю самое то:)
Еще такой вопрос возник: вот например я использую 1 вариант и ставлю выполнение скрипта на паузу, всё это зацикливаю. Отправилась порция, пауза, отправилась порция, пауза и т.д. Как по лимитам гугла это будет? Пока скрипт «спит» — не идет серверное время? Время исполнения считается? Или как?
Вопрос к тому, что, например, есть база в 3 000 чел. настроил на отправка 30 / пауза. Это 100 пауз. Не будет такого, что на 25 или 45 цикле выполнение остановится и все, дальше не пойдет?
Тут только тестировать «нагрузку» при квотах — https://developers.google.com/apps-script/guides/services/quotas?hl=ru
Я бы, наверное, создал условно 3000 записей «самого себя» и начал пробовать отправку одного сообщения себе эти все 3000 раз, заодно бы указывая номер элемента массива в сообщении, чтоб если что понять в какой момент сломалась отправка.
Ок, благодарю за совет) Нужно провести эксперимент 🙂