ИП Горелов Максим Николаевич 8 (915) 093-74-75
Добавляем данные в удаленный промпт с помощью скрипта. Например, один из скриптов забирает задачи из Google календаря или производит парсинг информации с сайта или соцсети или из RSS новостей и т.д. Так же с помощью нашего бота помощника можно сформировать скрипт для других целей.
Если у вас не получается самостоятельно с помощью бота создать нужный скрипт, напишите в группу https://t.me/+KQ6mUbTAtFQ4YWEy и мы поможем.
Загрузка задачи из Google календаря
function updateCalendarEvents() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Найдите начало и конец блока контента
var startElement = body.findText('##WEB_CONTENT_START##');
var endElement = body.findText('##WEB_CONTENT_END##');
if (!startElement || !endElement) {
Logger.log('Не найдены маркеры начала или конца блока контента');
return;
}
var startIndex = body.getChildIndex(startElement.getElement().getParent());
var endIndex = body.getChildIndex(endElement.getElement().getParent());
// Удалите существующий контент между маркерами
for (var i = endIndex - 1; i ﹥ startIndex; i--) {
body.removeChild(body.getChild(i));
}
// Получите события календаря на следующие 60 дней
var calendar = CalendarApp.getDefaultCalendar();
var now = new Date();
var endDate = new Date(now.getTime() + (60 * 24 * 60 * 60 * 1000)); // 60 дней вперед
var events = calendar.getEvents(now, endDate);
// Вставьте события в документ
for (var i = 0; i ﹤ events.length; i++) {
var event = events[i];
var title = event.getTitle();
var startTime = event.getStartTime();
var endTime = event.getEndTime();
var description = event.getDescription() || 'Нет описания';
var eventString = Utilities.formatDate(startTime, Session.getScriptTimeZone(), 'dd.MM.yyyy HH:mm') + ' - ' +
Utilities.formatDate(endTime, Session.getScriptTimeZone(), 'HH:mm') + '\n' +
title + '\n' +
description + '\n\n';
body.insertParagraph(startIndex + i + 1, eventString);
}
}
function createDailyTrigger() {
// Удаляем все существующие триггеры
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i ﹤ triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Создаем новый ежедневный триггер на 6:00 утра
ScriptApp.newTrigger('updateCalendarEvents')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}
Загрузка данных из Google Sheets
function updateGoogleSheetData() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Найдите начало и конец блока контента
var startElement = body.findText('##TABLE_START##');
var endElement = body.findText('##TABLE_END##');
if (!startElement || !endElement) {
Logger.log('Не найдены маркеры начала или конца блока контента');
return;
}
var startIndex = body.getChildIndex(startElement.getElement().getParent());
var endIndex = body.getChildIndex(endElement.getElement().getParent());
// Удалите существующий контент между маркерами
for (var i = endIndex - 1; i > startIndex; i--) {
body.removeChild(body.getChild(i));
}
// Получите данные из Google Таблицы
var sheetId = '1-XXXXXXXXXXXXXXXXXXXXXXXXX'; // Замените на ID вашей таблицы
var range = 'Лист1!A1:B300'; // Замените на нужный диапазон
var sheet = SpreadsheetApp.openById(sheetId).getSheetByName('Лист1');
var data = sheet.getRange(range).getValues();
// Вставьте данные в документ
var insertIndex = startIndex + 1; // Увеличиваем индекс на 1, чтобы начать вставку после ##TABLE_START##
for (var i = 0; i < data.length; i++) {
var row = data[i].filter(cell => cell).join(' | ').trim();
if (row) {
body.insertParagraph(insertIndex++, row);
}
}
}
function createDailyTrigger() {
// Удаляем все существующие триггеры
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Создаем новый ежедневный триггер на 6:00 утра
ScriptApp.newTrigger('updateGoogleSheetData')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}
Отправка уведомлений в Телеграмм
function sendTelegramMessage(chatId, message) {
if (!message) {
Logger.log("Ошибка: текст сообщения пустой");
return;
}
if (!chatId) {
Logger.log("Ошибка: chatId не задан");
return;
}
var token = "7615497024:XXXXXXXXXXXXXXXXXXXXXXX"; // Вставь сюда токен своего Telegram-бота
var url = "https://api.telegram.org/bot" + token + "/sendMessage";
var payload = {
method: "POST",
contentType: "application/json",
payload: JSON.stringify({
chat_id: chatId,
text: message,
}),
muteHttpExceptions: true
};
try {
var response = UrlFetchApp.fetch(url, payload);
Logger.log("Ответ от Telegram: " + response.getContentText());
} catch (e) {
Logger.log("Ошибка при отправке сообщения в Telegram: " + e.toString());
}
}
function doPost(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Проекты");
var data = JSON.parse(e.postData.contents);
if (!data.message || !data.message.chat || !data.message.chat.id || !data.message.text) {
Logger.log("Ошибка: данные сообщения не полные");
return;
}
var chatId = data.message.chat.id;
var messageText = data.message.text;
var range = sheet.getDataRange();
var values = range.getValues();
for (var i = 1; i < values.length; i++) {
if (values[i][10] == chatId) { // Колонка K — chatId
sheet.getRange(i + 1, 9).setValue(messageText); // Колонка I — Отчета
sendTelegramMessage(chatId, "Ваш отчет успешно получен!");
break;
}
}
}
function sendMessageToProTalk(chatId, messageId, message) {
var apiKey = "11622_IXEpowAR8ZvHkjYs6WOgvsq6TBV4KCKy"; // Вставьте ваш API ключ
var url = "https://api.pro-talk.ru/api/v1.0/send_message";
var payload = {
"chat_id": chatId,
"message_id": messageId,
"message": message
};
var options = {
method: "POST",
contentType: "application/json",
headers: {
"Authorization": "Bearer " + apiKey
},
payload: JSON.stringify(payload)
};
try {
var response = UrlFetchApp.fetch(url, options);
Logger.log("Ответ от ProTalk: " + response.getContentText());
} catch (e) {
Logger.log("Ошибка при отправке сообщения в ProTalk: " + e.toString());
}
}
function sendMessagesFromSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Проекты");
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
var chatId = data[i][10]; // Колонка K — chat_id
var messageId = "m" + (i + 1); // Создаем уникальный message_id
var message = "Напоминание: осталось менее часа до дедлайна вашей задачи!";
if (chatId) {
sendMessageToProTalk(chatId, messageId, message);
} else {
Logger.log("Пропущена строка " + (i + 1) + ": chatId пустой");
}
}
}
Загрузка данных с сайта
function updateWebsiteContent() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Найдите начало и конец блока контента
var startElement = body.findText('##WEBSITE_CONTENT_START##');
var endElement = body.findText('##WEBSITE_CONTENT_END##');
if (!startElement || !endElement) {
Logger.log('Не найдены маркеры начала или конца блока контента');
return;
}
var startIndex = body.getChildIndex(startElement.getElement().getParent());
var endIndex = body.getChildIndex(endElement.getElement().getParent());
// Удалите существующий контент между маркерами
for (var i = endIndex - 1; i > startIndex; i--) {
body.removeChild(body.getChild(i));
}
// Получите HTML-контент с вашего сайта
var url = 'https://wl.atiks.org/ru'; // Замените на ваш URL
var response;
try {
response = UrlFetchApp.fetch(url);
if (response.getResponseCode() !== 200) {
Logger.log('Ошибка получения контента: ' + response.getResponseCode());
return;
}
var html = response.getContentText();
if (!html) {
Logger.log('Получен пустой контент');
return;
}
} catch(e) {
Logger.log('Ошибка при запросе: ' + e.toString());
return;
}
var text = extractTextFromHtml(html);
// Проверка на пустой текст после обработки
if (!text.trim()) {
Logger.log('После обработки HTML получен пустой текст');
return;
}
// Вставьте очищенный текст в документ
body.insertParagraph(startIndex + 1, text);
Logger.log('Контент успешно обновлен');
}
function extractTextFromHtml(html) {
// Удаляем все HTML-теги
var text = html
// Удаляем style и script теги с содержимым
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
// Заменяем основные HTML-элементы на переносы строк
.replace(/<div>/ig, '\n')
.replace(/<\/div>/ig, '\n')
.replace(/<\/li>/ig, '\n')
.replace(/<li>/ig, ' * ')
.replace(/<\/ul>/ig, '\n')
.replace(/<\/p>/ig, '\n')
.replace(/<br\s*[\/]?>/gi, '\n')
// Удаляем все оставшиеся HTML-теги
.replace(/<[^>]+>/ig, '');
// Декодируем HTML-сущности
text = text
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/'/g, "'");
// Удаляем лишние пробелы и пустые строки
text = text
.replace(/\n\s*\n/g, '\n') // Удаляем множественные пустые строки
.replace(/^\s+|\s+$/g, '') // Удаляем пробелы в начале и конце
.replace(/[ \t]+/g, ' '); // Заменяем множественные пробелы одним
return text;
}
function createDailyTrigger() {
// Удаляем все существующие триггеры
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Создаем новый ежедневный триггер на 6:00 утра
ScriptApp.newTrigger('updateWebsiteContent')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
Logger.log('Ежедневный триггер успешно создан на 6:00');
}
// Дополнительная функция для тестирования
function testUpdate() {
updateWebsiteContent();
}
Получение данных из Airtable
function updateAirtableData() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Найдите начало и конец блока контента
var startElement = body.findText('##AIRTABLE_START##');
var endElement = body.findText('##AIRTABLE_END##');
if (!startElement || !endElement) {
Logger.log('Не найдены маркеры начала или конца блока контента');
return;
}
var startIndex = body.getChildIndex(startElement.getElement().getParent());
var endIndex = body.getChildIndex(endElement.getElement().getParent());
// Удалите существующий контент между маркерами
for (var i = endIndex - 1; i > startIndex; i--) {
body.removeChild(body.getChild(i));
}
// Получите данные из Airtable с фильтром
var apiKey = 'patXXXXXXX.XXXXXXXXXXX';
var baseId = 'appXXXXXXXXX';
var tableName = 'Functions';
var url = `https://api.airtable.com/v0/${baseId}/${encodeURIComponent(tableName)}?fields[]=${encodeURIComponent('Название')}&fields[]=${encodeURIComponent('Описание')}&filterByFormula=${encodeURIComponent('AND({Public}=2)')}`;
var options = {
method: 'get',
headers: {
'Authorization': `Bearer ${apiKey}`
}
};
var response = UrlFetchApp.fetch(url, options);
//Logger.log(response);
var data = JSON.parse(response.getContentText());
// Вставьте данные в документ
data.records.forEach(function(record) {
var title = record.fields['Название'] || 'Нет названия';
var description = record.fields['Описание'] || 'Нет описания';
//var recordString = `Название: ${title}\nОписание: ${description}\n\n`;
var recordString = `${title}\r${description.substring(0, 200)}...\r---\r`;
body.insertParagraph(startIndex + 1, recordString);
});
}
function createDailyTrigger() {
// Удаляем все существующие триггеры
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Создаем новый ежедневный триггер на 6:00 утра
ScriptApp.newTrigger('updateAirtableData')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}
загрузка данных из mindmap
// Константа с URL документа Google Drive
const SOURCE_DOCUMENT_URL = 'https://drive.google.com/file/d/1eNlTsocTxZ7x1aRvTLocLRpN_ukmZUR7/view';
// Функция для извлечения ID из URL Google Drive
function extractGoogleDriveFileId(url) {
const regex = /[-\w]{25,}/;
const match = url.match(regex);
return match ? match[0] : null;
}
function updateEmbeddedDoc() {
// Получаем ID документа
const fileId = extractGoogleDriveFileId(SOURCE_DOCUMENT_URL);
if (!fileId) {
Logger.log('Не удалось извлечь ID документа из URL');
return;
}
// Формируем URL API с полученным ID
const apiUrl = `https://functions.pro-talk.ru/api/v1.0/google_drive_mindmap?id=${fileId}&format=txt`;
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Найдите начало и конец блока контента
var startElement = body.findText('##EMBED_START##');
var endElement = body.findText('##EMBED_END##');
if (!startElement || !endElement) {
Logger.log('Не найдены маркеры начала или конца блока контента');
return;
}
var startIndex = body.getChildIndex(startElement.getElement().getParent());
var endIndex = body.getChildIndex(endElement.getElement().getParent());
// Убедитесь, что индексы корректны
if (startIndex >= endIndex) {
Logger.log('Некорректные индексы для вставки текста');
return;
}
// Удалите существующий контент между маркерами
for (var i = endIndex - 1; i > startIndex; i--) {
body.removeChild(body.getChild(i));
}
// Получите текст через API
var content = fetchTextFromApi(apiUrl);
// Вставьте текст в текущий документ
if (content) {
body.insertParagraph(startIndex + 1, content);
}
}
function fetchTextFromApi(url) {
try {
var response = UrlFetchApp.fetch(url);
var jsonResponse = JSON.parse(response.getContentText());
//Logger.log(jsonResponse);
// Проверяем наличие данных в ответе
if (!jsonResponse) {
Logger.log('Ответ API не содержит данных');
return null;
}
// Извлекаем текст из JSON-ответа
var textContent = jsonResponse;
// Выводим содержимое в лог для отладки
Logger.log('Получен текст из API:', textContent);
return textContent;
} catch (error) {
Logger.log('Ошибка при получении или обработке данных из API: ' + error.toString());
return null;
}
}
function createDailyTrigger() {
// Удаляем все существующие триггеры
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// Создаем новый ежедневный триггер на 6:00 утра
ScriptApp.newTrigger('updateEmbeddedDoc')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}
запустить телефонный звонок
function runVoximplantCall() {
var vox_cred = {
"account_email": "user@mail.com",
"account_id": 3333333,
"key_id": "22520be0-1a99-4c09-a155-770776366657",
"private_key": "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-----END PRIVATE KEY-----\n"
}
// URL вашего API
var url = "https://eu1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Европейский сервер
// var url = "https://us1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Сервер в США (альтернативный вариант)
// Данные для отправки в API
var payload = {
"vox_cred": vox_cred, // Учетные данные Voximplant
"phone": "7912XXXXXXX", // Номер телефона, на который будет совершен звонок
"bot_id": 12345, // ID бота, который будет использоваться для звонка
"bot_token": "XXXXXXXXXXXXXXXXXXXXXXX", // Токен бота для аутентификации
"elevellabs_api": "XXXXXXXXXXXXXXXXXXXXXX", // API ключ для Elevellabs (синтез речи)
"elevellabs_voice_id": "XXXXXXXXXXXXXX", // ID голоса для синтеза речи
"hello_text": "Привет! Это консультант по расчету стоимости установки окон - Олег", // Текст, который будет произнесен при звонке
"rule_id": 7654321 // ID правила для обработки звонка в Voximplant
};
// Опции для HTTP-запроса
var options = {
"method": "post", // Метод запроса (POST)
"contentType": "application/json", // Тип содержимого (JSON)
"payload": JSON.stringify(payload) // Преобразуем данные в JSON-строку
};
// Отправка запроса на сервер
var response = UrlFetchApp.fetch(url, options);
// Логирование ответа от сервера
Logger.log(response.getContentText()); // Выводим ответ в лог для отладки
}
активация бота по расписанию
function callRunDialogPost() {
var url = 'https://eu1.api.pro-talk.ru/api/v1.0/run_dialog_post'; // Европа
//var url = 'https://us1.api.pro-talk.ru/api/v1.0/run_dialog_post'; // США
var payload = {
bot_id: 14783, // Замените на ваш bot_id
bot_token: 'XXXXXXXXXXXXXXXXXXXXXXXXX', // Замените на ваш bot_token
user_messages: ['Привет', 'Как дела?', 'Расскажи шутку'] // Ваши сообщения боту
//user_messages: 'Привет##Как дела?##Расскажи шутку' // Так тоже можно
};
var options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
};
var response = UrlFetchApp.fetch(url, options);
var jsonResponse = JSON.parse(response.getContentText());
Logger.log(jsonResponse);
}
запуск исходящего диалога бота
function addQueuePost() {
// Запуск диалога через 10 минут от текущего времени:
const offsetMinutes = 10; // Смещение в минутах
const formattedDate = getFormattedDate(offsetMinutes);
console.log(formattedDate); // Выведет дату в формате ISO 8601
const url = "https://eu1.api.pro-talk.ru/api/v1.0/add_queue_post";
const data = {
bot_id: 14896, // Номер бота
bot_token: "XXXXXXXXXXXXXXXXXXXX", // Токен бота
channel: "WhatsApp", // Канал коммуникации: "WhatsApp" или "Telegram"
bot_task: "Узнать как дела", // Задача для бота в данном диалоге (не обязательно)
hello_text: "Привет!", // Первая фраза бота (не обязательно)
start_date: formattedDate, // Дата запуска диалога
phone: "79122710303", // Телефон, по которому напишет бот (обязателен, если не задан ник в Телеграм)
telegram: "@User", // Ник в телеграм, по которому напишет бот (обязателен, если не задан телефон)
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(data),
};
const response = UrlFetchApp.fetch(url, options);
Logger.log("Response Code: " + response.getResponseCode());
Logger.log("Response Body: " + response.getContentText());
}
function getFormattedDate(minutesOffset) {
const now = new Date();
const offsetMillis = minutesOffset * 60 * 1000;
const newDate = new Date(now.getTime() + offsetMillis);
const year = newDate.getFullYear();
const month = String(newDate.getMonth() + 1).padStart(2, '0');
const day = String(newDate.getDate()).padStart(2, '0');
const hours = String(newDate.getHours()).padStart(2, '0');
const minutes = String(newDate.getMinutes()).padStart(2, '0');
const seconds = String(newDate.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.000Z`;
}
заполнение Google форм
function doPost(e) {
var data = JSON.parse(e.postData.contents);
var formUrl = data.formUrl;
// Открываем форму по URL
var form = FormApp.openByUrl(formUrl);
// Получаем все элементы формы
var items = form.getItems();
// Создаем ответ на форму
var formResponse = form.createResponse();
// Перебираем все ключи в JSON-запросе (кроме formUrl)
for (var key in data) {
if (key === "formUrl") continue; // Пропускаем поле formUrl
// Ищем поле в форме с таким же названием
var fieldItem = items.find(function(item) {
return item.getTitle() === key;
});
// Если поле найдено, заполняем его
if (fieldItem) {
var value = data[key];
// В зависимости от типа поля, заполняем его
switch (fieldItem.getType()) {
// Текстовые поля
case FormApp.ItemType.TEXT:
formResponse.withItemResponse(fieldItem.asTextItem().createResponse(value));
break;
case FormApp.ItemType.PARAGRAPH_TEXT:
formResponse.withItemResponse(fieldItem.asParagraphTextItem().createResponse(value));
break;
// Выбор из списка
case FormApp.ItemType.LIST:
case FormApp.ItemType.DROPDOWN:
formResponse.withItemResponse(fieldItem.asListItem().createResponse(value));
break;
// Множественный выбор
case FormApp.ItemType.MULTIPLE_CHOICE:
case FormApp.ItemType.RADIO:
formResponse.withItemResponse(fieldItem.asMultipleChoiceItem().createResponse(value));
break;
case FormApp.ItemType.CHECKBOX:
formResponse.withItemResponse(fieldItem.asCheckboxItem().createResponse(value));
break;
// Шкалы
case FormApp.ItemType.SCALE:
case FormApp.ItemType.LINEAR_SCALE:
formResponse.withItemResponse(fieldItem.asScaleItem().createResponse(value));
break;
// Дата и время
case FormApp.ItemType.DATE:
formResponse.withItemResponse(fieldItem.asDateItem().createResponse(new Date(value)));
break;
case FormApp.ItemType.TIME:
// Преобразуем строку времени в объект Date
var timeParts = value.split(":");
var hours = parseInt(timeParts[0]);
var minutes = parseInt(timeParts[1]);
var date = new Date();
date.setHours(hours, minutes, 0, 0); // Устанавливаем часы и минуты
formResponse.withItemResponse(fieldItem.asTimeItem().createResponse(date));
break;
case FormApp.ItemType.DATETIME:
formResponse.withItemResponse(fieldItem.asDateTimeItem().createResponse(new Date(value)));
break;
case FormApp.ItemType.DURATION:
formResponse.withItemResponse(fieldItem.asDurationItem().createResponse(value));
break;
// Сетки
case FormApp.ItemType.GRID:
// Преобразуем объект в массив строк
var gridResponses = [];
for (var row in value) {
gridResponses.push(value[row]); // Добавляем выбранное значение для каждой строки
}
formResponse.withItemResponse(fieldItem.asGridItem().createResponse(gridResponses));
break;
case FormApp.ItemType.CHECKBOX_GRID:
// Для CHECKBOX_GRID передаем массив массивов
formResponse.withItemResponse(fieldItem.asCheckboxGridItem().createResponse(value));
break;
// Файлы (требует предварительной загрузки файлов в Google Drive)
case FormApp.ItemType.FILE_UPLOAD:
// Для загрузки файлов нужно использовать Google Drive API
// Пример: https://developers.google.com/apps-script/advanced/drive
break;
// Неизвестные типы полей
default:
// Пропускаем неизвестные типы полей
break;
}
}
}
formResponse.submit();
return ContentService.createTextOutput(JSON.stringify({result: "success", message: "Форма успешно заполнена"})).setMimeType(ContentService.MimeType.JSON);
}
отправка сообщений пассивному клиенту
// === Константы ===
const SHEET_NAME = "Лист2";
const BOT_TOKEN = "J5vOrREGQpy68Jl3QQBuxxxxxxxxxxxx";
const API_ENDPOINT = "https://chain.pro-talk.ru/celery/tasks";
// Конфигурация напоминаний по времени неактивности (в минутах)
const REMINDERS = [
{ min: 10, message: 'Вы давно не писали. Мы здесь, если у вас есть вопросы!' },
{ min: 30, message: 'Все еще ждем вашего ответа. Как мы можем помочь?' },
{ min: 90, message: 'Похоже, наш диалог завершен. Если у вас еще есть вопросы, просто напишите!' }
];
function checkInactiveDialogs() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const data = sheet.getDataRange().getDisplayValues();
const now = new Date();
const lastMessages = {};
const properties = PropertiesService.getScriptProperties();
// Загружаем список уже отправленных напоминаний
let sentReminders = {};
try {
const stored = properties.getProperty('sentReminders');
if (stored) {
sentReminders = JSON.parse(stored);
}
} catch (e) {
sentReminders = {};
properties.setProperty('sentReminders', '{}');
}
// Парсим данные
data.slice(1).forEach((row, index) => {
const [dateStr, chatId, userId, userMsg, botMsg, , botId] = row;
const date = parseDateGMT3(dateStr);
if (!date) return;
if (!lastMessages[chatId]) {
lastMessages[chatId] = {
firstDate: date,
rowNumber: index + 2,
botId,
userId,
lastUserMessage: null
};
}
if (userMsg?.trim()) {
lastMessages[chatId].lastUserMessage = date;
lastMessages[chatId].userId = userId;
lastMessages[chatId].botId = botId;
}
if (botMsg?.trim()) {
lastMessages[chatId].botId = botId;
}
});
// Проверяем каждый диалог
Object.entries(lastMessages).forEach(([chatId, dialog]) => {
const lastActive = dialog.lastUserMessage || dialog.firstDate;
const inactiveMin = Math.floor((now - lastActive) / 60000);
// Сортируем от меньшего к большему, чтобы проверять в правильном порядке
const sortedReminders = [...REMINDERS].sort((a, b) => a.min - b.min);
for (const reminder of sortedReminders) {
const key = `${chatId}_${reminder.min}`;
// Пропускаем, если уже отправляли это напоминание
if (sentReminders[key]) continue;
if (inactiveMin >= reminder.min) {
const success = sendReminderMessage({
chat_id: chatId,
bot_id: dialog.botId,
message: reminder.message
});
if (success) {
sentReminders[key] = true; // Помечаем как отправленное
properties.setProperty('sentReminders', JSON.stringify(sentReminders));
// Если это последнее напоминание (90 мин), больше не обрабатываем этот чат
if (reminder.min === REMINDERS[REMINDERS.length - 1].min) {
console.log(`Диалог ${chatId} завершён. Больше не будет напоминаний.`);
}
}
}
}
});
}
// Парсинг даты из строки с GMT+3
function parseDateGMT3(str) {
const date = str ? new Date(str + ' GMT+3') : null;
return date && !isNaN(date.getTime()) ? date : null;
}
// Отправка напоминающего сообщения
function sendReminderMessage({ chat_id, bot_id, message }) {
const eta = new Date().toISOString()
.replace('Z', '')
.replace(/\.\d{3}/, '.000') + '+03:00';
console.info('Отправка напоминающего сообщения:', chat_id, bot_id, message);
const payload = {
trigger_id: `inactivity_${Date.now()}`,
bot_id,
bot_token: BOT_TOKEN,
task_type: 'api_call',
parameters: {
api_url: 'https://eu1.api.pro-talk.ru/api/v1.0/run_function',
payload: {
function_id: 165,
functions_base_id: 'appkq3HrzrxYxoAV8',
bot_id,
bot_token: BOT_TOKEN,
chat_id,
arguments: {
role: 'assistant',
bot_reply: 0,
add_in_messages: 1,
message,
dialogs_api_host: 'eu1.api.pro-talk.ru'
}
},
method: 'POST'
},
eta,
repeat: 'Once'
};
const options = {
method: 'post',
contentType: 'application/json',
muteHttpExceptions: true,
payload: JSON.stringify(payload)
};
try {
const response = UrlFetchApp.fetch(API_ENDPOINT, options);
return true;
} catch (e) {
Logger.log('Ошибка отправки для chat_id %s: %s', chat_id, e.toString());
return false;
}
}
Комбайн для массовых задач
Этот скрипт превращает Google Таблицу в мощный инструмент для массовой автоматизации задач с ботами ProTalk. Ключевые особенности:
Массовая обработка — отправка множества задач одновременно.
Асинхронность — каждая задача выполняется независимо, без ограничений по времени.
Многошаговые сценарии — поддержка цепочек сообщений (
разделитель ##
). Автоматический контроль статусов — отслеживание выполнения задач (
Sent
, Waiting
, Completed
, Error
). Работа с API ProTalk — интеграция через вебхуки и запросы статусов.
Преимущества
Масштабируемость — можно запускать сотни задач одновременно.
Неограниченное время выполнения — задачи выполняются асинхронно, без таймаутов.
Многошаговые сценарии — поддержка сложных диалогов с ботами.
Контроль через таблицу — все статусы и ответы видны в реальном времени.
Технические детали
- Использует API ProTalk (replica_webhook_mass
и replica_get_reply
).
- Работает в Google Apps Script (триггеры или ручной запуск).
- Оптимизирован для массовых запросов (пакетная отправка + задержки между проверками). Итог
Этот инструмент превращает Google Таблицу в мощный комбайн для автоматизации любых задач с ботами ProTalk — от рассылок до сложных диалоговых сценариев. Просто добавь задачи в таблицу — остальное сделает скрипт!
function addGoOnTuesdays() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const range = sheet.getRange("A1:C3");
const today = new Date();
if (today.getDay() === 2) {
range.setValue("go");
}
}
function processTasks() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const configSheet = ss.getSheetByName("Боты ProTalk");
const taskSheet = ss.getSheetByName("Задачи");
const taskData = taskSheet.getDataRange().getValues();
const botConfigs = getBotConfigs(configSheet);
const COLUMNS = {
EMPLOYEE_ID: 0,
MESSAGES: 1,
RESPONSES: 2,
FLAG: 3,
STATUS: 4,
TASK_ID: 5,
STEP_INDEX: 6,
CHAT_ID: 7
};
const tasksToSend = {};
const repliesToCheck = [];
for (let i = 1; i < taskData.length; i++) {
try {
const row = i + 1;
const employeeId = taskData[i][COLUMNS.EMPLOYEE_ID];
const fullMessage = taskData[i][COLUMNS.MESSAGES];
const currentResponse = taskData[i][COLUMNS.RESPONSES];
const flag = taskData[i][COLUMNS.FLAG];
const currentStatus = taskData[i][COLUMNS.STATUS];
const taskId = taskData[i][COLUMNS.TASK_ID];
const stepIndex = taskData[i][COLUMNS.STEP_INDEX];
const chatId = taskData[i][COLUMNS.CHAT_ID];
if (!employeeId || !flag || currentStatus === 'Completed') continue;
const botConfig = botConfigs[employeeId];
if (!botConfig) continue;
const messages = fullMessage ? fullMessage.split('##').map(m => m.trim()).filter(m => m) : [];
if (messages.length === 0) continue;
if (!currentStatus || currentStatus.startsWith('Error')) {
const timestamp = Math.floor(new Date().getTime() / 1).toString();
const uuid = Utilities.getUuid().split('-')[0];
const newChatId = `long_task_api_${timestamp}_${employeeId}`;
const newTaskId = Utilities.getUuid();
Utilities.sleep(200);
if (!tasksToSend[employeeId]) {
tasksToSend[employeeId] = {
botConfig: botConfig,
requests: []
};
}
tasksToSend[employeeId].requests.push({
row: row,
chatId: newChatId,
taskId: newTaskId,
message: messages[0],
stepIndex: 1,
updates: {
STATUS: 'Sent',
TASK_ID: newTaskId,
STEP_INDEX: 1,
RESPONSES: '',
CHAT_ID: newChatId,
FLAG: 'Task started'
}
});
}
else if (currentStatus === 'Sent' || currentStatus === 'Waiting') {
repliesToCheck.push({
row: row,
employeeId: employeeId,
chatId: chatId,
taskId: taskId,
currentResponse: currentResponse,
stepIndex: stepIndex,
messages: messages,
botConfig: botConfig
});
}
} catch (error) {
taskSheet.getRange(i + 1, COLUMNS.STATUS + 1).setValue(`Error: ${error.message}`);
}
}
sendBatchMessages(tasksToSend, taskSheet, COLUMNS);
checkIndividualReplies(repliesToCheck, taskSheet, COLUMNS);
}
function sendBatchMessages(tasksToSend, taskSheet, COLUMNS) {
for (const [employeeId, data] of Object.entries(tasksToSend)) {
if (data.requests.length === 0) continue;
const { botConfig, requests } = data;
const { apiToken, apiHost } = botConfig;
const url = `https://${apiHost}/api/v1.0/replica_webhook_mass?` +
`promt_id=${encodeURIComponent(employeeId)}&` +
`api_token=${encodeURIComponent(apiToken)}`;
const payload = requests.map(req => ({
message: {
message_id: req.taskId,
chat: { id: req.chatId },
text: req.message
}
}));
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true,
timeout: 1
};
try {
UrlFetchApp.fetch(url, options);
} catch (error) {}
data.requests.forEach(req => {
updateTaskRow(taskSheet, req.row, COLUMNS, req.updates);
});
Utilities.sleep(200);
}
}
function checkIndividualReplies(repliesToCheck, taskSheet, COLUMNS) {
for (const task of repliesToCheck) {
try {
const { botConfig, employeeId, chatId, taskId } = task;
const reply = getReply(botConfig.apiToken, employeeId, chatId, taskId, botConfig.apiHost);
processReply(
taskSheet,
task.row,
COLUMNS,
botConfig,
employeeId,
{
currentResponse: task.currentResponse,
stepIndex: task.stepIndex,
messages: task.messages,
chatId: chatId,
taskId: taskId
},
reply
);
Utilities.sleep(500);
} catch (error) {
taskSheet.getRange(task.row, COLUMNS.STATUS + 1)
.setValue(`Error checking reply: ${error.message}`);
}
}
}
function getReply(apiToken, employeeId, chatId, messageId, apiHost) {
const url = `https://${apiHost}/api/v1.0/replica_get_reply?` +
`promt_id=${encodeURIComponent(employeeId)}&` +
`api_token=${encodeURIComponent(apiToken)}`;
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({
message: {
message_id: messageId,
chat: {id: chatId}
}
}),
muteHttpExceptions: true,
timeout: 5000
};
const response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() === 200) {
const responseData = JSON.parse(response.getContentText());
return responseData.message || '';
} else {
throw new Error(`API error: ${response.getResponseCode()} - ${response.getContentText()}`);
}
}
function processReply(taskSheet, row, COLUMNS, botConfig, employeeId, req, reply) {
if (reply && reply !== '' && !reply.includes('/restart')) {
const updatedResponse = req.currentResponse ? req.currentResponse + '\n\n---\n\n' + reply : reply;
taskSheet.getRange(row, COLUMNS.RESPONSES + 1).setValue(updatedResponse);
if (req.stepIndex < req.messages.length) {
const nextTaskId = Utilities.getUuid();
const newChatId = req.chatId;
const updates = {
STATUS: 'Sent',
TASK_ID: nextTaskId,
STEP_INDEX: req.stepIndex + 1
};
updateTaskRow(taskSheet, row, COLUMNS, updates);
const sendUrl = `https://${botConfig.apiHost}/api/v1.0/replica_webhook?` +
`promt_id=${encodeURIComponent(employeeId)}&` +
`api_token=${encodeURIComponent(botConfig.apiToken)}`;
const sendOptions = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({
message: {
message_id: nextTaskId,
chat: { id: newChatId },
text: req.messages[req.stepIndex]
}
}),
muteHttpExceptions: true,
timeout: 2000
};
UrlFetchApp.fetch(sendUrl, sendOptions);
} else {
taskSheet.getRange(row, COLUMNS.STATUS + 1).setValue('Completed');
}
} else {
const waitingMessage = reply === '' ? 'Waiting for response...' : reply;
taskSheet.getRange(row, COLUMNS.STATUS + 1).setValue('Waiting');
taskSheet.getRange(row, COLUMNS.RESPONSES + 1).setValue(waitingMessage);
}
}
function updateTaskRow(taskSheet, row, COLUMNS, updates) {
const sheet = taskSheet;
for (const [colName, value] of Object.entries(updates)) {
const colIndex = COLUMNS[colName];
sheet.getRange(row, colIndex + 1).setValue(value);
}
}
function getBotConfigs(configSheet) {
const data = configSheet.getDataRange().getValues();
const botConfigs = {};
for (let i = 1; i < data.length; i++) {
const employeeId = data[i][0];
if (employeeId) {
botConfigs[employeeId] = {
apiToken: data[i][1],
apiHost: data[i][2] || 'us1.api.pro-talk.ru'
};
}
}
return botConfigs;
}
© ProTalk 2023-2025 ИП Горелов Максим Николаевич ИНН 500104951533 ОГРН 309500106900065