ИП Горелов Максим Николаевич 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; } }
напоминание от бота по логам бота
// === Константы === const SHEET_NAME = "Лист3"; const BOT_TOKEN = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; const API_ENDPOINT = "https://chain.pro-talk.ru/celery/tasks"; // Конфигурация напоминаний по времени неактивности (в минутах) const REMINDERS = [ { min: 2, 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 = {}; // Парсим данные 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, // Добавили: время последнего сообщения пользователя sentLevels: [] }; } if (userMsg?.trim()) { lastMessages[chatId].lastUserMessage = date; // Запоминаем последнее сообщение пользователя lastMessages[chatId].userId = userId; lastMessages[chatId].botId = botId; lastMessages[chatId].sentLevels = []; // Сбрасываем напоминания при новом сообщении } 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) => b.min - a.min); let sent = false; for (const reminder of sortedReminders) { if (inactiveMin >= reminder.min && !dialog.sentLevels.includes(reminder.min)) { const success = sendReminderMessage({ chat_id: chatId, bot_id: dialog.botId, message: reminder.message }); if (success) { dialog.sentLevels.push(reminder.min); } sent = true; break; // Отправляем только одно, наиболее актуальное напоминание } } if (!sent) { console.log(`Для chat_id ${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); 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, channel: 'umnico', dialogs_api_host: 'eu1.api.pro-talk.ru' }, 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) }; console.info('payload', payload, payload.parameters.payload.arguments); //return true; 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; }
ИП Горелов Максим Николаевич ИНН 500104951533 ОГРН 309500106900065