Справочник по Google Apps Script:
основные команды для автоматизации Гугл Таблиц

30 декабря 2020, автор: Елена Позднякова
Здравствуйте, уважаемые читатели моего блога!
Данная статья-справочник подготовлена для тех, кто хочет автоматизировать Гугл Таблицы с помощью языка программирования Google Apps Script.
Это можно сделать даже с нуля и без начальных навыков программирования, но, единственная проблема в том, что не так-то просто разобраться в официальной документации от Гугла: во-первых, она на английском, во-вторых, очень объемная.
Здесь вы найдете четко структурированный набор самых нужных функций, которые позволят вам свободно ориентироваться в автоматизации Google Таблиц.
Скачайте схему, смотрите видео, если есть вопросы - задавайте в комментариях!
Google Apps Script основные команды для Гугл Таблиц
Оглавление
Видео:
1

Имена: как получить доступ или обратиться к таблице, листу, диапазону, ячейке

Таблица как электронный файл (Spreadsheet)

1
Доступ к таблице по URL:
SpreadsheetApp.openByUrl ( url ) - доступ к электронной таблице по URL-адресу.

var ss = SpreadsheetApp.openByUrl ( 'https://docs.google.com/spreadsheets/d/abc1234567/edit');

Logger.log ( ss.getName ( ) );
~
2
Доступ к таблице по ID:
SpreadsheetApp.openById ( id ) - доступ к электронной таблице по ID.

Как получить ID таблицы из URL-адреса:
https://docs.google.com/spreadsheets/d/1bwOBqlm4RJgLLlftChrOTcZgDlZrvjA9fddybkgrto8/edit#gid=0
https://docs.google.com/spreadsheets/d/spreadsheetId/edit#gid=0

var ss = SpreadsheetApp.openById ( "1bwOBqlm4RJgLLlftChrOTcZgDlZrvjA9fddybkgrto8" );

Logger.log (ss.getName ( ) );
~

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

~
3
Доступ к родительской таблице из контейнерного скрипта:
SpreadsheetApp.getActive ( ) - возвращает родительскую электронную таблицу для контейнерного скрипта.
SpreadsheetApp.getActiveSpreadsheet ( ) - дубль предыдущей функции, возвращает родительскую электронную таблицу для контейнерного скрипта.

//например, получить URL адрес текущей таблицы

SpreadsheetApp.getActive ( ).getUrl ( );
SpreadsheetApp.getActiveSpreadsheet ( ).getUrl ( );
~
4
Открыть таблицу, записанную в переменную
~
Вспомогательные функции:
ss.getUrl ( ) - получить URL-адрес таблицы
ss.getId ( )
- получить ID таблицы
ss.getName ( ) - получить имя таблицы

ss.getFormUrl ( ) - возвращает URL-адрес формы, которая отправляет ответы в таблицу или null.
Если форм несколько, использовать sheet.getFormUrl ( )
~

Лист (Sheet)

1
Доступ к листу по имени:
getSheetByName ( name) - доступ к листу по имени.

var sheet = SpreadsheetApp.getActiveSpreadsheet ( ) .getSheetByName ( "Лист2" )
~
2
Доступ к активному листу для контейнерного скрипта:
getActiveSheet ( ) - доступ к активному листу в электронной таблице.
Активный лист в электронной таблице - это лист, который отображается в пользовательском интерфейсе электронной таблицы.

var sheet = SpreadsheetApp.getActiveSpreadsheet ( ). getActiveSheet ( ); 

//напрямую без указания таблицы тоже можно:

var sheet = SpreadsheetApp.getActiveSheet ( ); 
~
Вспомогательные функции:
sheet.getName ( ) - возвращает имя листа =sheet.getSheetName()
sheet.getSheetId ( ) - возвращает ID листа
range.getSheet ( ) - возвращает лист (как объект), к которому принадлежит диапазон

Получить все листы таблицы в виде списка:
ss.getSheets ( ) - получить все листы таблицы в виде списка.
Возможен доступ по индексам. Индексация начинается с 0.

var sheets = SpreadsheetApp.getActiveSpreadsheet ( ) .getSheets ( ) ;

 Logger.log (sheets [0].getName ( ) ); //Лист1
sheet.getIndex ( ) - получает позицию листа в родительской электронной таблице. Начинается с 1.

sheet.getFormUrl ( ) - возвращает URL-адрес формы, которая отправляет ответы в этот лист или null.
~

Диапазон (Range)

1
Получить диапазон по номеру строки и столбца:
getRange(row, column, numRows, numColumns) - возвращает диапазон с верхней левой ячейкой в заданных координатах с заданным количеством строк и столбцов.

Пример.
Вывести в журнал все значения из приведенного диапазона:
Скрипт:

function showRange () {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[0];
  var range = sheet.getRange(1, 2, 3, 2);
  var values = range.getValues();
  
  // Напечатать в журнал значения из диапазона с помощью цикла
  for (var row in values) {
    for (var col in values[row]) {
      Logger.log(values[row][col]);
    }
  }
}
Результат:
~
2
Получить диапазон по нотации А1:
Один из вариантов обозначения диапазонов - Нотация А1.
Это строка типа Sheet1!A1:B2.
getRange ( a1Notation ) - получить диапазон по обозначению с помощью нотации А1. Нотация А1 представляет из себя строку и должна быть заключена в кавычки: " " (двойные), ' ' (одинарные) или ` ` (обратные).

Например:

var range = sheet.getRange('B1:C3').getValues ( );
С помощью обратных кавычек `` в текст нотации можно встраивать выражения и переменные:

var row = 3;
var cell = sheet.getRange( `D${row}` ).getValue ( );
Синтаксис для встраивания ${переменная}.
В пределах обратных кавычек не должно быть лишних пробелов, иначе нотация не будет прочитана.
~
3
Получить доступ к активному диапазону из контейнерного скрипта:
getActiveRange ( ) - возвращает выбранный диапазон на активном листе или null, если активного диапазона нет. Метод класса SpreadsheetApp.

Примечание: в пользовательской функции диапазон относится к ячейке, которая пересчитывается.


var color = SpreadsheetApp.getActiveRange().getBackgroundColor();
~
4
Получить доступ к активному диапазону из независимого скрипта:
getActiveRange ( ) - возвращает выбранный диапазон на активном листе или null, если активного диапазона нет. Метод классов: Spreadsheet, Sheet.

SpreadsheetApp.openById('16tBD3fhiKUFI1mqEyorn6ZqXm0OU3frjLcN8veNiZPg').getActiveRange ( );
5
Получить столбец:
getRange (row, column, numRows) - возвращает диапазон с верхней левой ячейкой с заданными координатами и с заданным количеством строк.

Примечание: только один столбец.

Пример.
Получить данные столбца, начиная с ячейки B2 до последней заполненной строчки в таблице.
Скрипт:

function showColumn (){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[1];

  var range = sheet.getRange(2, 2, 5);
  var values = range.getValues();
  
    for (var row in values) {
    for (var col in values[row]) {
      Logger.log(values[row][col]);
    }
  }
}
Результат:
~
6
Получить столбец по нотации А1:
getRange ( a1Notation ) - получить столбец по обозначению с помощью нотации А1.

var range = sheet.getRange('B:B').getValues ( );
7
Получить ряд:
getRange(row, column, numRows, numColumns) - возвращает диапазон с верхней левой ячейкой в заданных координатах с заданным количеством строк и столбцов.

Пример.
Вывести в журнал данные строки №4 из таблицы:
Скрипт:

function showRow (){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[1];
  
  var range = sheet.getRange(4, 1, 1,3);
  var values = range.getValues();
  
   for (var row in values) {
    for (var col in values[row]) {
      Logger.log(values[row][col]);
    }
  }
}
Результат:
~
8
Получить ряд по нотации А1:
getRange ( a1Notation ) - получить ряд по обозначению с помощью нотации А1.

var range = sheet.getRange('4:4').getValues ( );
~
Вспомогательные функции:
range.getA1Notation ( ) - возвращает строковое описание диапазона в нотации А1
range.getRow ( ) - возвращает номер строки для заданной ячейки
range.getColumn ( ) - возвращает номер ряда для заданной ячейки
sheet.getLastRow ( ) - возвращает номер последнего ряда на листе в котором есть контент
sheet.getLastColumn ( ) - возвращает номер последнего столбца на листе в котором есть контент

range.getNumRows ( ) - возвращает количество строк в диапазоне
range.getNumColumns ( ) - возвращает количество столбцов в диапазоне

range.offset(rowOffset, columnOffset) - возвращает новый диапазон, который смещен от этого диапазона на заданное количество строк и столбцов (которое может быть отрицательным). Новый диапазон имеет тот же размер, что и исходный.
~

Ячейка (Cell)

1
Получить ячейку по номеру строки и столбца:
getRange ( row, column ) - получить ячейку по номеру ряда и столбца на листе (индексация начинается с 1).

//из таблицы контейнерного скрипта получить лист с индексом 1 (это второй лист)

var sheet = SpreadsheetApp.getActive().getSheets()[1];

//из ячейки по адресу: РЯД 3 СТОЛБЕЦ 4 получить значение

var cell = sheet.getRange (3, 4) .getValue ( ) ;
~
2
Получить ячейку по нотации А1:
getRange ( a1Notation ) - получить ячейку по обозначению с помощью нотации А1. Нотация А1 представляет из себя строку и должна быть заключена в кавычки: " " (двойные), ' ' (одинарные) или ` ` (обратные).

var cell = sheet.getRange( 'D3' ).getValue ( );
С помощью обратных кавычек `` в текст нотации можно встраивать выражения и переменные:

var row = 3;
var cell = sheet.getRange( `D${row}` ).getValue ( );
Синтаксис для встраивания ${переменная}.
В пределах обратных кавычек не должно быть лишних пробелов, иначе нотация не будет прочитана.
~
3
Получить доступ к активной ячейке из контейнерного скрипта:
getCurrentCell ( ) - возвращает текущую ячейку на активном листе или null, если текущая ячейка отсутствует.

var number   = SpreadsheetApp.getCurrentCell ( ).getValue ( );
~
4
Получить доступ к активной ячейке из независимого скрипта:
getActiveCell ( ) - возвращает активную ячейку на листе.

Примечание: в документации Гугл указано, что предпочтительно использовать getCurrentCell ( )

SpreadsheetApp.openById('16tBD3fhiKUFI1mqEyorn6ZqXm0OU3frjLcN8veNiZPg').getActiveCell().getValue();
~
Вспомогательные функции:
range.getA1Notation ( ) - возвращает строковое описание диапазона в нотации А1
range.getRow ( ) - возвращает номер строки для заданной ячейки
range.getColumn ( ) - возвращает номер ряда для заданной ячейки
sheet.getLastRow ( ) - возвращает номер последнего ряда на листе в котором есть контент
sheet.getLastColumn ( ) - возвращает номер последнего столбца на листе в котором есть контент

range.offset(rowOffset, columnOffset) - возвращает новую ячейку, которая смещена от текущей ячейки на заданное количество строк и столбцов (которое может быть отрицательным).
~
2

Основные команды

Структура команд:
сначала навигация - а затем команда
1
Класс SpreadsheetApp
главный класс для управление Гугл Таблицами

Все команды начинаются с:

SpreadsheetApp.
2
Класс Spreadsheet
электронная таблица как документ

openByUrl (url)
openById (id)
getActive ( )
getActiveSpreadsheet ( )
3
Класс Sheet
лист

getSheetByName (name)
getActiveSheet ( )




4
Класс Range
диапазон или ячейка

getRange(row, column, numRows, numColumns)
getRange (row, column, numRows)
getRange ( row, column )
getRange ( a1Notation )
getActiveRange ( )
getCurrentCell ( )
getActiveCell ( )
Обычно обращение к классам SpreadsheetApp и Spreadsheet записывают в переменную ss, обращение к классу Sheet в переменную sheet, обращение к классу Range в переменную range.

//Текущая Гугл Таблица записана в переменную:
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  //Лист с именем "Лист1" записан в переменную:
  var sheet = ss.getSheetByName("Лист1"); 
  
  //Диапазон A1:B4 записан в переменную:
  var range = sheet.getRange('A1:B4');
Для контейнерного скрипта допустимо прямое обращение к активному листу или активному диапазону (минуя промежуточные классы), так как скрипт создан из родительской таблицы:

SpreadsheetApp.getActiveSheet ( );

//или

SpreadsheetApp.getActiveRange ( );
SpreadsheetApp.getCurrentCell ( );

//для независимого скрипта требуется обязательное обращение к таблице по ID или URL

SpreadsheetApp.openByUrl ( 'URL' ).getActiveCell ( ); 

//или

SpreadsheetApp.openById ( 'ID' ).getSheetByName ( 'name').getRange ( ); 
~
Основные команды класса SpreadsheetApp
Вставить строки
sheet.insertRowBefore ( beforePosition )
sheet.insertRowAfter ( afterPosition )
sheet.insertRows ( rowIndex )
sheet.insertRows ( rowIndex, numRows )
sheet.insertRowsBefore ( beforePosition, howMany )
sheet.insertRowsAfter ( afterPosition, howMany )

Вставить столбцы
sheet.insertColumnBefore ( beforePosition )
sheet.insertColumnAfter ( afterPosition )
sheet.insertColumns ( columnIndex )
sheet.insertColumns ( columnIndex,numColumns )
sheet.insertColumnsBefore ( beforePosition, howMany )
sheet.insertColumnsAfter ( afterPosition, howMany )
Переименовать
Удалить
Лист
ss.deleteSheet ( sheet )

Удалить одну строку
sheet.deleteRow ( rowPosition )

Удалить несколько строк
sheet.deleteRows ( rowPosition, howMany )

Удалить один столбец
sheet.deleteColumn ( columnPosition )

Удалить несколько столбцов
sheet.deleteColumns ( columnPosition, howMany )

Удалить диапазон ячеек
range.deleteCells ( shiftDimension)
~
Самые частые команды:

getValue ( ) - setValue ( )
getValues ( ) - setValues ( )
Еще важные команды:

range.getNote - range.setNote
range.getNotes - range.setNotes

activate ( ) - активировать, команда эквивалентна щелчку мышью
(применима к листу, диапазону, ячейке)
~
Ссылки на официальную документацию по методам классов SpreadsheetApp, Spreadsheet, Sheet, Range
~

Функции других служб для Гугл Таблиц: почта, календари, переводчик и т.д.

1
Отправить письмо из Гугл Таблицы

MailApp.sendEmail(recipient, subject, body)
sendEmail(recipient, subject, body) - отправляет электронное сообщение.

Пример скрипта:

function myMail() {
  MailApp.sendEmail("test@test.ru", "лови письмо из гугл таблицы",
  "еее! Всё получилось!!!")
}
Видеоинструкция по функции sendEmail:
~
2
Отправить событие из Гугл Таблицы в публичный календарь

CalendarApp.getCalendarById("ID").createEvent(title, startTime, endTime)
createEvent(title, startTime, endTime) - cоздает новое событие в календаре.

Пример скрипта:

function CalendarEvent(){
  var ss = SpreadsheetApp.getActiveSpreadsheet(),
      sheet = ss.getSheetByName('Лист2'),
   
      title = sheet.getRange('A2').getValue(),
      startTime = sheet.getRange('B2').getValue(),
      endTime = sheet.getRange('C2').getValue();
  CalendarApp.getCalendarById("ID").createEvent(title, startTime, endTime);
}  
Видеоинструкция по функции createEvent:
~
3
Поисковые запросы в Google Apps Script

sheet.createTextFinder(findText).findNext()
createTextFinder(findText) - cоздает средство поиска текста для диапазона, которое может находить и заменять текст в этом диапазоне. Метод классов Spreadsheet, Sheet, Range.

findNext() - возвращает следующую ячейку, соответствующую критериям поиска. Метод класса TextFinder.

Все методы класса TextFinder.

Фрагмент скрипта:

//Создаем поисковый запрос со значением текущего менеджера
  var textFinder = sheet.createTextFinder(currentManager);
  
  //Запускаем поисковый запрос с помощью встроенной функции findNext() и полученное значение записываем в переменную
  //Функцию можно запускать без условий, так как поисковый запрос точно не пустой
  var firstOccurrence = textFinder.findNext();
Видеоинструкция по поисковым запросам в Google Apps Script:
~
4
Переводчик в Google Apps Script

LanguageApp.translate(text, sourceLanguage, targetLanguage) 
translate(text, sourceLanguage, targetLanguage) - автоматически переводит текст с исходного языка на целевой.

Коды языков здесь.

Пример скрипта:

function translate (){
  var arabic = LanguageApp.translate('Привет', 'ru', 'ar');
  Logger.log(arabic);
} 

// مرحبا
~
5
Сообщение alert для Гугл Таблицы
alert(prompt) - функция alert вызывает окно с сообщением, которое блокирует интерфейс таблицы до тех пор, пока пользователь не нажмет ОК.

Для того, чтобы вызвать данную функцию из Гугл Таблицы, требуется дополнительно обратиться к интерфейсу таблицы с помощью встроенной функции от Гугл: SpreadsheetApp.getUi().
getUi() - функция обращается к пользовательскому интерфейсу Гугл Таблицы.

Вот так выглядит скрипт:

function alertSpreadsheet () {
 SpreadsheetApp.getUi().alert("Привет! Это сообщение выведено с помощью функции alert")
 }
Видеоинструкция по данному скрипту:
~
6
Получить email пользователя, запустившего скрипт
getEmail() - получает email пользователя текущей сессии.

function CheckMyEmail() {
  var email = Session.getActiveUser().getEmail()
  Logger.log('Твой email: ' + email)
 }
~
7
Получить текущую дату и время
Описание, как работать с датой и временем в рекламных скриптах Гугл.

function todayNow () {
  var today = Date ();
  Logger.log(today)
  }

//Tue Dec 29 2020 17:53:43 GMT+0300 (Москва, стандартное время)

function todayNow2() {
  var today = new Date().toLocaleString('ru');
  Logger.log(today)
  }

//29.12.2020, 18:06:08

Математические операции с датами. В приведенном ниже фрагменте скрипта переменная yesterday выражает время ровно 24 часа назад от текущего момента.

function yesterday (){
  var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
  var now = new Date();
  var yesterday = new Date(now.getTime() - MILLIS_PER_DAY);
  Logger.log(yesterday)
}

//Mon Dec 28 18:02:23 GMT+03:00 2020

~
2

Триггеры, которые запускают функции

Простые триггеры

Мы разберем два простых триггера: onEdit (срабатывает при любом изменении в Гугл Таблице) и onOpen (срабатывает при открытии).
Начнем с функции onEdit, а функцию onOpen рассмотрим в следующем разделе для целей создания пользовательского меню.

OnEdit (e) - функция onEdit является простым триггером. Выполняется при редактировании ячейки.
Синтаксис:

function onEdit(e) {
команда
}

onEdit - это зарезервированное имя функции, для триггера нужно использовать только его;
e - это не обязательный аргумент, который передает информацию об объекте события.

Перечень объектов, которые содержатся в e, можно посмотреть здесь.
Например:
  • range - ячейка или диапазон ячеек, который был изменен (обратиться к диапазону: e.range);
  • value - новое значение для ячейки (доступно только для редактирования одиночной ячейки, обратиться к значению: e.value);
  • sourse - Гугл Таблица (обратиться: e.sourse).
Пример1.
Триггер добавляет примечание к любой ячейке, которая была изменена.

function onEdit(e) {
  
  var range = e.range;
  
   range.setNote('Последнее изменение: ' + new Date().toLocaleString('ru'))
  }
~
Пример2.
Триггер добавляет примечание, если была изменена ячейка из столбца 2.

function onEdit(e) {
  
  var range = e.range;
  
  if (range.getColumn() == '2')
    
  {range.setNote('Последнее изменение: ' + new Date().toLocaleString('ru'))
  }
}

~
Пример3.
Триггер записывает время последнего изменения в ячейку справа от измененной ячейки.

function onEdit(e) {
  
// Это контейнерный скрипт:
// e = SpreadsheetApp.getActiveRange()
  
  var range = e.range,
      row =   range.getRow(),
      column =   range.getColumn();
  
  
  range.offset(0, 1).setValue(new Date().toLocaleString('ru'))
  
}
~

Пользовательское меню

С помощью Google Apps Script можно создать пользовательское меню для Гугл Таблицы. Создание меню доступно только из Контейнерного скрипта.
Меню Гугл Таблицы будет видеть только тот, кто имеет право редактирования (пользователи с доступом на просмотр меню не видят).

Вот так выглядит простейшее пользовательское меню:
Скрипт:

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('МОЁ МЕНЮ')
    .addItem('Запустить функцию 1', 'myFunction1')
    .addSeparator()
    .addItem( 'Запустить функцию 2', 'myFunction2')
    .addToUi();
}
А здесь пользовательское меню для Гугл Документа, которое включает подменю:
Скрипт:

function onOpen() {
  var ui = DocumentApp.getUi();
  // или SpreadsheetApp или FormApp.
  ui.createMenu('ПОЛЬЗОВАТЕЛЬСКОЕ МЕНЮ')
  .addItem('Пункт меню1', 'function1')
  .addItem('Пункт меню2', 'function2')
  .addSeparator()
  .addSubMenu(ui.createMenu('Подменю')
              .addItem('Пункт подменю 1', 'function3')
              .addItem('Пункт подменю 2', 'function4'))
  .addToUi();
}
Функция onOpen является простым триггером. Выполняется при открытии документа.
Авторизация пользователя для запуска этой функции не требуется, поэтому возможности её ограничены лишь несколькими действиями.
В частности, для onOpen доступно создание пользовательского меню для Гугл Таблицы.

addItem ( caption, functionName ) - функция добавляет элемент меню
caption - название элемента меню
finctionName - функция, которую запускает элемент меню
~

Кнопки

Функции можно запускать из редактора скриптов с помощью кнопки "Выполнить":
Кроме этого, команду на выполнение функции можно перенести в Гугл Таблицу, чтобы функция запускалась из таблицы (не заходя в редактор скриптов).
Для этого в Гугл Таблице нужно создать кнопку и назначить на неё скрипт.
Кнопкой может быть любое изображение, вставленное поверх ячеек, или кнопкой может быть рисунок.

ВставкаИзображение Изображение поверх ячеек
или
Вставка Рисунок (нарисовать любую фигуру)

дальше одинаково:
• • • → Назначить скриптВписать название функции без ( )
~

Управление триггерами вручную

  1. Откройте проект Apps Script.
  2. Слева нажмите Триггеры.
  3. В правом нижнем углу нажмите Добавить триггер .
  4. Выберите и настройте тип триггера, который вы хотите создать.
  5. Щелкните Сохранить .
~

Программное управление триггерами

Создавать триггеры можно программно с помощью метода:

ScriptApp.newTrigger(functionName) - возвращает TriggerBuilder

Пример.

function createTimeDrivenTrigger() {
  
  // Триггер сработает каждое утро в 09:00.
  ScriptApp.newTrigger('myFunction')
      .timeBased()
      .onWeekDay(ScriptApp.WeekDay.MONDAY)
      .atHour(9)
      .create();
}
~

Пользовательские функции

Видео инструкция:
Пишем свою пользовательскую функцию для Гугл Таблиц с помощью Google Apps Script
Пример пользовательской функции "Перевод километров в морские мили":

//Пользовательская функция для встроенного скрипта "Перевод километров в морские мили"
//Комментарий ниже является аннотацией для создания подсказок

/**
* Функция переводит километры в морские мили.
*
* @param {number} kilometers километры.
* @return {number} nauticalMiles морские мили.
* @customfunction
*/

function kilometersToNauticalMiles ( kilometers ) {
  var nauticalMiles = kilometers/1.8522;
  return nauticalMiles
}
~
4

Полезные фишки
и ссылки на дополнительные материалы по JavaScript

Макросы - основа скрипта для умных лентяев

Запишите макрос, и скрипт сам запомнит ваши действия и сделает из них сценарий:
Инструменты → Макросы → Запись макроса
Особенно хороши макросы для подготовки сценариев с форматированием данных. Цвет, размер текста, форматы - все это удобнее ПОКАЗАТЬ, чем искать соответствующие команды.
Настройку проверки данных (выбор значений из списка, флажок) тоже удобнее настраивать через макрос, чем искать в командах.

Записанный сценарий ждет вас в меню:
Инструменты → Редактор скриптов
~

Google Apps Script? Сейчас объясню!
[подборка видео от Автоматизации без обязательств]

~

JavaScript - основа Google Apps Script

JavaScript на одном листе

JavaScript

Учебник по JavaScript

~

Полный курс по JavaScript за 6 часов
от Владилена Минина

~