• Отправить email из Google таблиц по условию?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Вот рабочий код:
    /**
     * Конфигурация.
     */
    const CONFIG = {};
    
    // Рабочие листы
    CONFIG.sheets = [
      `Журнал заказов`
    ];
    
    CONFIG.cols = {};
    
    // Порядок столбцов
    CONFIG.cols[`Заказ`] = 1;
    CONFIG.cols[`Email`] = 2;
    CONFIG.cols[`Статус`] = 3;
    
    
    
    /**
     * Отправляет письмо при смене статуса.
     * 
     * @param {Object} event
     */
    function onEditTrigger(event = {}) {
      try {
        if (!event.source)
          event.source = SpreadsheetApp
            .getActiveSpreadsheet();
    
        if (!event.range)
          event.range = SpreadsheetApp
            .getActiveRange();
    
        // Открыть текущий лист
        const sheet = event.source
          .getActiveSheet();
    
        // Обходим листы не из списка
        if (!CONFIG.sheets.includes(sheet.getName()))
          return;
    
        // Номер активной строки
        const row = event.range
          .getRow();
    
        // Обходим закрепленные строки
        if (row <= sheet.getFrozenRows())
          return;
    
        // Номер активного столбца
        const col = event.range
          .getColumn();
    
        // Обхотим столбцы (не статус)
        if (col !== CONFIG.cols[`Статус`])
          return;
    
        // Получаем данные строки
        const values = sheet
          .getRange(`${row}:${row}`)
          .getValues()[0];
    
        const recipient = values[CONFIG.cols[`Email`] - 1];
        const status = values[CONFIG.cols[`Статус`] - 1];
    
        const subject = `Заголовок письма`;
        const body = `Здравствуйте, статус вашего заказа изменен на "${status}"`;
    
        // Отправляем письмо
        const response = MailApp
          .sendEmail(recipient, subject, body);
    
        if (!response)
          throw new TypeError(`Не удалось отправить письмо!`);
    
        console.log(`Письмо успешно отправлено!`, recipient, subject, body);
      }
      catch (error) {
        console.error(error.stack);
    
        throw new Error(error.toString());
      }
    };


    Чтобы это сработало нужно:
    1. Открыть редактор скриптов в таблице и вставить туда код выше.
    617ff57ce528c038052771.png

    2. Создать новый триггер
    617ff5559eed5232989193.png

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как достать нужные данные из UrlFetchApp или JSON.parse?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Вы двигаетесь в верном напралвении, но есть несколько замечаний.

    Первое, url запроса (в вашем случае: 'https://api.telegram.org/bot' + token + '/') не содержит функцию для запуска.
    Должно быть так:
    'https://api.telegram.org/bot' + token + '/getUpdates'

    или
    'https://api.telegram.org/bot' + token + '/getMe'
    или
    'https://api.telegram.org/bot' + token + '/sendMessage'

    и тд.

    Второе замечание, data - второй параметр для функции fetch(url, data) класса UrlFetchApp должен быть объектом.
    И желательно чтобы в этом объекте метод запроса был post.
    В идеале вод так:
    {
      "method": `POST`,
      "contentType": `application/json`,
      "muteHttpExceptions": true,
      "payload": JSON.stringify(data)
    }

    При этом payload указываем если вы хотите что-то передать телеграму (зависит от выбранной функции в url запроса).

    Третье, После того как метод fetch(url, data) вернул результат response, из него нужно вытащить данные методом response .getContentText(). И аш потом уже парсить через JSON.parse.

    Полный код выглядит так:
    /**
       * Простой метод проверки токена авторизации вашего бота. Не требует параметров. Возвращает основную информацию о боте в виде объекта User.
       * 
       * @return {Object.User}
       */
      function getMe() {
        const response = UrlFetchApp
          .fetch(
            `https://api.telegram.org/bot${TOKEN}/getMe`,
            {
              "method": `POST`,
              "contentType": `application/json`,
              "muteHttpExceptions": true
            }
          );
    
        let result = response
          .getContentText();
    
        result = JSON
          .parse(result);
    
        if (!result.ok)
          throw new Error(result.description);
    
        return result;
      }

    Только не забудьте заменить слово TOKEN на имя переменной с вашим токеном.

    Для отправки сообщения в бот используйте:
    /**
       * Используйте этот метод для отправки текстовых сообщений. В случае успеха отправленное сообщение возвращается.
       * 
       * @param {String|Number} chatId Уникальный идентификатор целевого чата или имя пользователя целевого канала (в формате @channelusername)
       * @param {String} text Текст отправляемого сообщения, 1-4096 знаков после синтаксического анализа сущностей.
       * @param {Object} options
       * 
       * @return {Object.Message}
       */
      function sendMessage(chatId, text, options) {
        if (!arguments.length)
          throw new TypeError(`Параметры () не соответствуют сигнатуре метода TelegramBot.sendMessage.`);
    
        const data = {};
    
        if (!([`string`, `number`].includes(typeof chatId) && String(chatId).trim().length))
          throw new TypeError(`Параметры (${typeof chatId}) не соответствуют сигнатуре метода TelegramBot.sendMessage.`);
    
        data.chat_id = String(chatId).trim();
    
        if (!([`string`, `number`].includes(typeof text) && String(text).trim().length))
          throw new TypeError(`Параметры (${typeof chatId}, ${typeof text}) не соответствуют сигнатуре метода TelegramBot.sendMessage.`);
    
        data.text = String(text).trim();
    
        if (options) {
          if (typeof options !== `object`)
            throw new TypeError(`Параметры (${typeof chatId}, ${typeof text}, ${typeof options}) не соответствуют сигнатуре метода TelegramBot.sendMessage.`);
    
          if ((item => typeof item === `string` && item.trim().length)(options.parse_mode))
            data.parse_mode = options.parse_mode;
    
          if (typeof options.entities === `object`)
            data.entities = options.entities;
    
          if (typeof options.disable_web_page_preview === `boolean`)
            data.disable_web_page_preview = options.disable_web_page_preview;
    
          if (typeof options.disable_notification === `boolean`)
            data.disable_notification = options.disable_notification;
    
          if ((item => typeof item === `number` && Number.isInteger(item) && item > 0)(options.reply_to_message_id))
            data.reply_to_message_id = options.reply_to_message_id;
    
          if (typeof options.allow_sending_without_reply === `boolean`)
            data.allow_sending_without_reply = options.allow_sending_without_reply;
    
          if (typeof options.reply_markup === `object`)
            data.reply_markup = options.reply_markup;
        }
    
        const response = UrlFetchApp
          .fetch(
            `https://api.telegram.org/bot${TOKEN}/sendMessage`,
            {
              "method": `POST`,
              "contentType": `application/json`,
              "muteHttpExceptions": true,
              "payload": JSON.stringify(data)
            }
          );
    
        let result = response
          .getContentText();
    
        result = JSON
          .parse(result);
    
        if (!result.ok)
          throw new Error(result.description);
    
        return result;
      }


    Ну и напоследок, если умеете пользоваться библиотеками gas вот моя для телеграм ботов:
    TelegramApi (1d78ytTohb5IBskJadNg1hwu-LRyqYONmnQQWbkVUlmX3Ft2Q6AEOgW9m)
    (она еще немного сырая, но юзать можно, документации пока нет, сори, открывайте код и читайте описание методов)

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    6 комментариев
  • Как заполнить ячейку по словарю?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    А может просто выпадающий список ?)

    Или нужно из строки в столбце name найти слово по словаю и если есть совпадение с брендом, записать этот бренд в столбец бренда соответствующей строки ?

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Почему не работает сценарий гугл, когда заходишь с другого устройства?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Простой триггер onEdit имеет ряд ограничений. Проверьте не попадаете ли вы под них.

    Маловероятно, но, также можете попробовать добавить, этот код, чтобы заставить задать область авторизации только для доступа к текущей таблице, а не все электронные таблицы:
    /**
     * @OnlyCurrentDoc
     */


    Если у вас по мимо этого кода есть еще какой-то, который требует авторизации. То необходимо дать пользователям возможность авторизоваться.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как обойти перезатирание строк при большом количестве вызовов скрипта, создающего новые строки в гугл таблице?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Вариант 1 - Использовать локсервис (подробнее в документации google appsscript)
    Вариант 2 - Сначала сохранять данные кеш скрипта, и настроить триггер который раз в минуту вытаскивает данные из кеша и кладет в таблицу.
    Вариант 3 - Использовать sheet.append()
    Вариант 4 - Сохранять не в таблицу а в базу данных
    Вариант 5 - После каждой вставки строки setValue или до, использовать SpreadsheetApp.flush() (но этот вариант не очень хорош сам по себе, его лучше использовать в совокупности с другими)

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    2 комментария
  • Возможно ли сделать рабочий скрипт на сортировку данных в таблице?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    1) "Скрипт который я указал в вопросе, ругается (TypeError: Cannot read property 'range' of undefined)" все верно, так и должно быть когда вы запускаете скрипт вручную. Переменная e, которую вы передаете в функцию, содержит данные только когда отрабатывает простой триггер onEdit но при ручном запуске этой функции она будет undefined.
    Добавьте в начало скрипта такую заглушку (что можно было запускать скрипт вручную):
    e = e || {
        range: SpreadsheetApp.getActiveRange()
      };

    Но тогда нужна будет авторизация в скрипте.
    Используйте установочный триггер на изменение в таблице.

    2) У вас рабочий скрипт, что не работает, не могу понять ?

    function onEdit(e) {
      e = e || {
        range: SpreadsheetApp.getActiveRange()
      };
      
      const range = e.range;
      
      if (range.getColumn() !== 5)
        return;
      
      if (range.getRow() < 3)
        return;
      
      const sheet = range.getSheet();
      
      const frozen_rows = 2;
      
      const last_row = sheet
      .getRange('E'+(frozen_rows+1))
      .getNextDataCell(SpreadsheetApp.Direction.DOWN)
      .getRow();
      
      sheet
      .getRange(frozen_rows+1, 2, last_row - frozen_rows, 13)
      .sort({column: 5, ascending: false});
    }


    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как создавать только 1 документ при переносе данных из гугл таблиц?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Как вариант в исходной гугл таблице добавьте столбец со статусом.
    При повторном запуске скрипта игнорируйте строки в который статус например "готово".
    Или сохраните id или ссылку на документ.

    Другой способ (хуже, но в некоторых случаях тоже имеет место быть), это засунуть в документ метку (мета данные разработчика), а перед созданием нового документа проверять есть ли такой документ на диске.

    Третий способ, удаляйте из гугл таблицы строку или переместите ее на соседний лист "Обработано".

    Четвертый способ, покажите модальное окно в котором спросите у пользователя какой диапазон строк в гугл таблице обрабатывать.

    ... и еще туева куча вариантов)

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
  • Как сделать разнесение ответов от разных отвечающих в разные гугл-таблицы?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Да можно, как самый простой из вариантов это:
    Сохраните результаты в гугл таблицу (стандартная опция). А затем из нее вытягивайте нужные данные формулой QUERY().

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как ссылаться на предыдущий лист в Google Sheets?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Получите индекс текущего листа:

    const ss = SpreadsheetApp.getActiveSpreadsheet()
    const sheet = ss.getActiveSheet();
    const index = sheet.getIndex();


    Затем получайте предыдущий лист по индексу
    ss. getSheets()[index - 2];

    Вам формула нужна?
    function getListPrev(range) {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const sheet = ss.getActiveSheet();
      const index = sheet.getIndex();
      
      const formula = sheet.getActiveRange().getFormula();
      range = formula.match(/getListPrev\(([^\)]*?)\)/i)[1].trim();
      
      const sheet_prev = (ss.getSheets()[index - 2] || null);
      
      if (!sheet_prev)
        throw new Error('Нет предыдущего листа!');
      
      return sheet_prev
      .getRange(range)
      .getValues();
    }


    5f76e926c261d767962334.png

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    5 комментариев
  • Как скриптом достать ссылку из ячейки в Google таблицах?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Может быть так?

    function myFunction() {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
      const sheet = ss.getSheetByName("Тест");
      
      const value = sheet.getRange(2, 1).getRichTextValue();
      
      sheet.getRange(3, 1).setRichTextValue(value);
    }


    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    1 комментарий
  • Как реализовать поиск в Google Таблице?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Если вы хотите сами это написать, изучайте документацию, что тут еще можно советовать.

    1. Получить номер активной строки
    2. Получить данные с другого листа до активной сроки.
    3. Перебрать данные и найти нужное.
    4. Все это писать в простом триггере onEdit

    Найдите разработчика, вряд ли вам кто-то будет писать код под формализованную задачу.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Можно ли сделать кнопку в ячейке, которая будет иметь три состояния (0, 1, 2) - смена состояния при нажатии?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Нет, НО.

    1. Вы не можете создавать кнопки внутри ячейках, кнопки-рисунки встраиваются поверх таблицы.
    2. У вас нет возможности скриптом манипулировать цветом для рисунков.

    Возможное решение.
    1. Создайте кнопку с прозрачным фоном.
    2. Закрасьте ячейку нужным цветом.
    3. Поместите кнопку точно над ячейкой.
    4. Создайте скрипт, который будет обращаться к кешсервису для обновления состояния.
    5. Назначьте скрипт на кнопку, который будет менять состояние и цвет.

    или
    4. Создайте скрипт, который будет сохранять состояние в ячейке цветом текста таким же как цвет фона.
    5. Создайте условное форматирование для этой ячейки.
    6. Назначьте скрипт для смены состояния.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как опубликовать Google таблицы средствами Google Apps Script?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Специально не пытался искать инфу по этому поводу.
    Но в официальной документации не видел подобных возможностей.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как защитить свои скрипты?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Ну ... по хорошему есть только два пути.

    Первый, вы создаете сторонний файл-скритпа, разворачиваете его как веб-приложение, а затем делаете на него запрос пост или гет.

    И второй, это опубликовать ваш скрипт как дополнение для документов.

    Хотя ... есть еще один, это в стороннем файле-скритпа установить нужные действия на триггер.

    В каждом из вариантов есть свои нюансы и их можно применить не во всех случаях.

    Это если вам нужно скрыть код от посторонних глаз.

    А если вы хотите просто запретить редактирование кода, то вы можете использовать библиотеки.

    Для более подробных разъяснений пишите мне лично.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как доработать скрипт на корректное заполнение листа в Google App Script?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Что-то вроде этого:
    (код не тестил, могут быть опечатки, но логика такая)

    function format() {
      const ss = SpreadsheetApp.getActiveSpreadsheet();
    
      const sourseList = ss.getSheetByName("Исходные данные");
      const bdList = ss.getSheetByName("БД");
    
      const values = sourseList.getDataRange()
      .getValues()
      .filter(item => item[4] === "0шт.")
      .map(item => [item[3], item[7]]);
    
      bdList.getRange(bdList.getLastRow() + 1, 1, values.length, 2).setValues(values);
    }


    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как найти userCodeAppPanel?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    С давних-давен в консоле браузера некорректно показывает ошибки в веб-приложениях гугл.
    userCodeAppPanel - это значить стандартный скрипт гугла, который он помещает в ваше веб приложение.
    И там же находиться и ваш код.

    Вывод, простой.
    Ищите ошибку в вашем коде.
    А чтобы было проще ее находить, используйте try catch

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    Комментировать
  • Как сделать Foreach по всем листам таблицы в Google Script Spreadsheets?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
      
    for(const sheet of sheets) {
      Logger.log(sheet.getName());
    }
      
    sheets.forEach(function(sheet) {
      Logger.log(sheet.getName());
    });
      
    while(sheets.length) {
      const sheet = sheets.shift();
        
      Logger.log(sheet.getName());
    }


    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
    1 комментарий
  • Почему замещаются и дублируются строчки при сортировке в гугл таблицах?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Скорее всего дубли возникают из-за одновременного запуска скрипта.
    Солидарен с Григорием попробуйте LockService за предотвращения колизии.

    Как вариант можете вынести сортировку в отдельную функцию и ее повесить на минутный триггер.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
  • Как сделать автоматическую сортировку в гугл-таблицах?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Если вы вешаете функцию на триггер, желательно использовать не
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    а
    var ss = SpreadsheetApp.openById( "тут ваш id таблицы");


    Тогда ошибок не должно быть (этот формат лучше подходит для установки на триггер).

    Хотя используя, скрипт внутри контейнера, и так не должны быть ошибки.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан
  • Как обойти ограничение выполнения скрипта в 6 минут?

    stomaks
    @stomaks
    Expert Google Apps Script (stomaks.me)
    Оптимизировать код!

    На каждом обращении к таблице вы теряете много времени getRange, getValue , setValue ...
    Лучше всего один раз получить все данные обработать и один раз положить обратно в таблицу, если это позволяют данные конечно же.

    Если коротко:
    Старайтесь минимизировать количество обращений к таблице/листам.

    ---
    Максим Стоянов (stomaks), pазработчик Google Apps Script.
    g-apps-script.com
    stomaks.me
    Ответ написан