Гугл скрипт

Командная строка и те невообразимые вещи, которые с ее помощью можно творить, — визитная карточка UNIX и всех ее наследников. А где есть командная строка, там есть скрипты. И сегодня… нет, мы не будем учиться писать скрипты, мы рассмотрим наиболее полезные из них, те, что ты сможешь применять ежедневно для решения самого разного круга задач, начиная от сводки погоды и веб-сервера в одну строку и заканчивая ботом для твиттера в десять строк и скриптом для автоматического запуска любого торрент-клиента.

Сразу оговорюсь, что я вовсе не приверженец шаманизма и ни в коем случае не призываю тебя сидеть в зелено-черной консоли и набирать кучу букв, чтобы выполнить действия, для которых в графическом интерфейсе достаточно навести мышку на нужный элемент. Однако я убежден, что для решения многих задач консоль и скрипты годятся гораздо лучше графического интерфейса и поэтому пренебрегать ими никак нельзя. Тем более что любая DE позволяет создать для скрипта иконку, так что для его запуска даже не надо будет открывать консоль.

Простые примеры

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

$ curl ifconfig.co

Эта простая команда покажет тебе внешний IP — идеальный вариант, если в Сеть ты ходишь через роутер. Все, что она делает, — просто обращается к серверу ifconfig.co, который возвращает обратно IP-шник одной строкой вместо полноценной веб-страницы.

И да, это вовсе не скрипт, это просто команда, но, чтобы превратить команду в скрипт, достаточно поместить ее в текстовый файл и первой строкой добавить так называемый шебанг, то есть символы #!, за которыми следует имя командного интерпретатора:

#!/bin/bashcurl ifconfig.co

Далее скрипт сохраняем в каталог ~/bin и назначаем права на исполнение:

$ chmod +x ~/bin/myip.sh

Теперь его можно вызывать из командной строки командой myip.sh.

Идем дальше.

#!/bin/shcurl -4 wttr.in/Moscow

Этот скрипт позволяет получить сводку погоды на четыре дня. Принцип тут такой же, как в случае с ifconfig.co.

Сводка погоды в консоли
Сводка погоды в консоли
Другие статьи в выпуске:

Xakep #212. Секреты даркнета

  • Подписка на «Хакер»
#!/bin/shdig +short txt $1.wp.dg.cx

А так можно получить краткое описание чего-либо в Википедии, причем с помощью DNS-запроса вместо обращения к веб-серверу. Кстати, веб-сервер через командную строку тоже очень легко создать:

#!/bin/shwhile ( nc -l 80 < file.html > : ) ; do : ; done

Данный скрипт основан на утилите netcat (nc), которую называют швейцарским армейским ножом для сетевых операций. Скрипт запускает цикл, выполняющий команду nc, которая слушает 80-й порт и в ответ на запрос отдает file.html, отправляя переданный запрос в никуда (символ означает noop, то есть пустую операцию).

С помощью простых скриптов и команд можно запросто слушать интернет-радио:

#!/bin/shmpv --volume=50 -playlist ~/16bit.fm_128.m3u

Естественно, плей-лист в формате M3U необходимо заранее скачать с сайта радиостанции. Кстати, если запустить MPlayer с аргументом —input-ipc-server=/tmp/mpvsocket, им можно будет управлять, записывая команды в файл. Например, настроить громкость:

echo 'volume +10' | socat - /tmp/mpvsocket

Создай два скрипта: один для запуска, другой для остановки радио (со строкой killall mpv), повесь их на рабочий стол и настрой горячие клавиши DE на управление воспроизведением. Вуаля, у тебя готов плеер для интернет-радио, запустить который можно, просто кликнув по иконке на рабочем столе. И он почти не будет расходовать память или занимать трей.

Но отвлечемся от сетевых операций и вернемся к локальным делам.

#!/bin/shtar -czf "../${PWD##*/}.tar.gz" .

Это один из моих любимых скриптов. Он создает архив tar.gz текущего каталога. Особого внимания здесь заслуживает конструкция ${PWD##*/}, которая берет полный путь до текущего каталога (переменная $PWD) и удаляет из него первую часть вплоть до последнего слеша, оставляя, таким образом, только имя самого каталога. Далее к нему добавляется расширение tar.gz. Более подробно о таких конструкциях ты можешь прочитать в man bash.

#!/bin/shwhile true; do inotifywait -r -e MODIFY КАТАЛОГ && ТВОЯ_КОМАНДАdone

А это уже скрипт, который запускает команду в ответ на изменение файлов в каталоге. Ее можно использовать для множества разных целей, например для автоматического включения плеера при сохранении MP3-файла. Или просто выводить уведомление на десктоп, используя в качестве команды notify-send:

notify-send "Файл изменен"

Десктоп

Раз уж мы заговорили о десктопе, то продолжим. Как и консоль, его тоже можно заскриптовать. Вот, например, скрипт, загружающий случайные обои, опубликованные на reddit-канале wallpaper:

#!/bin/bashwget -O - http://www.reddit.com/r/wallpaper |\ grep -Eo 'http://i.imgur.com+jpg' |\ shuf -n 1 |\ xargs wget -O background.jpgfeh --bg-fill background.jpg

Здесь все просто. С помощью wget скрипт загружает страницу www.reddit.com/r/wallpaper, передает ее grep, который ищет на ней ссылки на imgur, выбирает случайную ссылку с помощью shuf, загружает ее опять же с помощью wget и устанавливает в качестве обоев, используя команду feh (это такой миниатюрный просмотрщик изображений, его нужно предварительно установить). Скрипт можно добавить на рабочий стол, и тогда по клику у тебя будут меняться обои.

#!/bin/shstate=`synclient | grep TouchpadOff | cut -d '=' -f 2`if ; then synclient TouchpadOff=0else synclient TouchpadOff=1fi

А это скрипт для включения/выключения тачпада ноутбука: включает, если отключен, и наоборот. В своей работе использует утилиту synclient, позволяющую управлять тачпадами производства Synaptics (90% тачпадов делают они). При запуске без аргументов утилита выводит различную информацию о тачпаде, в том числе строку TouchpadOff = 1, если он активирован, и TouchpadOff = 2, если отключен. Скрипт находит это значение и в зависимости от состояния тачпада включает или отключает его.

!#/bin/bashmpv tv:// -frames 3 -vo jpegmv 00000003.jpg photo.jpgrm -f 0000*.jpg

А так можно сделать снимок с помощью веб-камеры. Скрипт использует видеоплеер mpv, чтобы записать первые три кадра, снятые камерой, в JPEG-файлы с именами 0000000.jpg, 00000002.jpg, 00000003.jpg, затем переименовывает третий снимок в файл photo.jpg, а остальные удаляет. Три снимка необходимы для того, чтобы камера успела провести инициализацию, обычно первые два получаются просто черными. Иногда изображение выходит перевернутым; чтобы это исправить, mpv следует запускать с флагом -vf flip:

$ mpv tv:// -frames 3 -vf flip -vo jpeg

Ту же самую команду можно использовать для создания полноценной камеры слежения, которая делает снимки в моменты, когда юзер прикасается к мыши:

#!/bin/bashwhile true; do sudo cat /dev/input/mouse0 | read -n1 mpv tv:// -frames 3 -vo jpeg mv 00000003.jpg `date +%F-%H-%M`.jpg rm -f 0000*.jpg sleep 10done

Скрипт входит в бесконечный цикл, ожидая данные на устройстве /dev/input/mouse0. Если данные есть, значит, мышь сдвинулась или была нажата одна из ее клавиш. После этого он использует mpv, чтобы сделать три снимка, дает третьему снимку имя текущей даты и удаляет остальные.

Для записи полноценного видео с веб-камеры можно использовать такой скрипт:

#!/bin/bashmencoder tv:// -tv driver=v4l2:width=800:height=600:device=/dev/video0:fps=30:outfmt=yuy2:forceaudio:alsa:adevice=hw.2,0 -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=1800 -ffourcc xvid -oac mp3lame -lameopts cbr=128 -o video.avi

В результате ты получишь video.avi в формате MPEG4 с битрейтом 1800 и аудиодорожкой в формате MP3 с битрейтом 128.

#!/bin/bashffmpeg -f x11grab -r 25 -s 1366x768 -i :0.0 screencast.mpg

А так ты можешь записать скринкаст. 1366×768 — разрешение рабочего стола. Просто сделать скриншот отдельного окна всегда можно с помощью команды import:

import screenshot.png

После ее запуска значок мыши изменится на «прицел», с помощью которого можно выбрать окно. Повесив эту команду на клавиатурную комбинацию, ты получишь практически идеальную систему снятия скриншотов, абсолютно не жрущую память, как это делают специализированные приложения, постоянно висящие в трее.

Подключить и настроить внешний монитор тоже можно из командной строки:

#!/bin/shif ; then exitfiif ; then xrandr --output VGA-0 --off xrandr -s 0else if ; then xrandr --output LVDS --auto --primary --output VGA-0 --auto --left-of LVDS xrandr --newmode "1920x1080" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync xrandr --addmode VGA-0 1920x1080 xrandr --output VGA-0 --mode 1920x1080fixrandr --dpi 96

Данный скрипт предполагает, что основной монитор носит имя LVDS, а внешний — VGA-0. Это стандартная ситуация для ноутбуков; если ты не уверен, можешь проверить вывод команды xrandr: при передаче скрипту аргумента off он отключает внешний монитор, аргумент on, в свою очередь, включает его, располагая по левую сторону от основного (аргумент —left-of LVDS в первой команде). Далее скрипт добавляет новую конфигурацию для монитора с разрешением 1920 x 1080 и активирует его. В самом конце скрипт устанавливает дефолтное значение DPI — как показывает практика, при подключении монитора с другим разрешением оно часто слетает.

На самом деле в большинстве случаев команды xrandr —newmode … и xrandr —addmode … не нужны, так как Xorg может получить конфигурацию монитора и поддерживаемые им разрешения с помощью EDID. Иногда, однако, этого не происходит, и строку конфигурации, указываемую после аргумента —newmode, приходится генерировать самостоятельно с помощью инструмента cvt:

$ cvt 1920 1080

Он же поможет сгенерировать нестандартное разрешение, «не поддерживаемое» монитором по умолчанию.

Google, Twitter, Dropbox и торренты

Отвлечемся от десктопных дел и поговорим о сетевых сервисах. Начнем, разумеется, с Google. Вот так будет выглядеть скрипт для получения первых десяти результатов поиска:

#!/bin/bashQ="$@"URL='https://www.google.de/search?tbs=li:1&q='AGENT="Mozilla/4.0"stream=$(curl -A "$AGENT" -skLm 10 "${GOOG_URL}${Q//\ /+}" | grep -oP '\/url\?q=.+?&amp' | sed 's|/url?q=||; s|&amp||')echo -e "${stream//\%/\x}"

Скрипт делает запрос к Google с помощью уже знакомого нам curl, заменяя пробелы в поисковой строке на плюсы. Далее выискивает в ответном HTML ссылки и выводит их на экран. Все просто, хоть и кажется сложным.

Ищем в Google из командной строки
Ищем в Google из командной строки

Второй популярный сервис — YouTube:

#!/bin/bashmpv -fs -quiet `youtube-dl -g "$1"`

Здесь все совсем просто. Скрипт всего лишь проигрывает видео с указанным в аргументе ID с помощью плеера mpv. Естественно, youtube-dl придется установить заранее.

Как насчет твиттера? Нет проблем, вот полноценный бот, который на входе принимает команду, выполняет ее с помощью командного интерпретатора и отправляет результат указанному юзеру.

#!/bin/bashUSER="ТВОЙ_НИК"while true; do CMD=`echo "/dma +1" | ttytter -script | sed 's/\\ //' if ; then REPL=`$CMD` echo "/dm $USER ${REPL:0:140}" | ttytter -script CMD = $OLD_COMD fi sleep 60done

Скрипт использует консольный клиент ttytter, читая в цикле последнее direct message, далее он проверяет, не была ли такая команда уже выполнена, и, если нет, выполняет ее и отправляет указанному в переменной USER пользователю, попутно обрезая до 140 символов.

Чтобы все заработало как надо, тебе придется установить ttytter, запустить его, ввести приведенную им ссылку в адресную строку браузера, скопировать показанный браузером ключ аутентификации и ввести его в ttytter. Естественно, перед тем как это сделать, следует завести для бота отдельного юзера и залогиниться под его учеткой.

Ttytter запрашивает ключ
Ttytter запрашивает ключ

Твиттер можно использовать не только для выполнения команд, но и для мониторинга машины. Следующий скрипт отправляет в ленту сообщение с информацией о состоянии машины (имя хоста, uptime, нагрузка, свободная память и нагрузка на CPU):

#!/bin/bashHOST=`hostname -s`UP=`uptime | cut -d" " -f4,5 | cut -d"," -f1`LOAD=`uptime | cut -d":" -f5,6`MEM=`ps aux | awk '{ sum += $4 }; END { print sum }'`CPU=`ps aux | awk '{ sum += $3 }; END { print sum }'`tweet="Host: ${HOST}, uptime: ${UP}, cpu: ${CPU}%, memory: ${MEM}%, loadavg ${LOAD}"if ; then echo "FATAL: The tweet is longer than 140 characters!" exit 1fiecho $tweet | ttytter -script
Мониторинг машины с помощью Twitter
Мониторинг машины с помощью Twitter

Ну и под конец приведу скрипт, не связанный с сетевыми сервисами, но имеющий прямое отношение к сетям и к тому, зачем мы обычно их используем. Это скрипт для запуска и остановки торрент-клиента во время простоя машины:

#!/bin/bashIDLE=600000STOPCMD="transmission-remote -S"STARTCMD="transmission-remote -s"STOPPED="yes"while true; do if ; then if ; then $STARTCMD STOPPED="no" fi else if ; then $STOPCMD STOPPED="yes" fi fi sleep 60done

Скрипт уходит в бесконечный цикл, каждую минуту проверяя, сколько миллисекунд прошло с момента, когда юзер что-либо делал (для этого используется команда xprintidle). Если прошло уже 600 000 мс (десять минут), скрипт выполняет команду, указанную в переменной STARTCMD. В противном случае он выполнит команду STOPCMD, но только тогда, когда до нее была выполнена команда STARTCMD. Если кратко: ничего не делаешь за компом десять минут — запускается STARTCMD, в данном случае это команда запуска всех закачек с помощью Transmission, если нет — приостановка всех закачек. Не любишь Transmission? Нет проблем, вот команды для Deluge:

STOPCMD="deluge-console pause \*"STARTCMD="deluge-console resume \*"

Вместо выводов

Не удивлюсь, если все описанное в статье покажется тебе очередным велосипедостроением, и даже соглашусь с таким мнением. Все-таки современный Linux — это не та система для сумасшедших экспериментаторов, какой она была пятнадцать лет назад. Сегодня для каждой задачи можно найти отдельный, отлаженный и хорошо работающий инструмент, в том числе графический. Другое дело, что не совсем понятно, стоит ли захламлять систему тяжеловесными написанными на Python приложениями с кучей зависимостей, когда ту же задачу легко решить с помощью простенького скрипта.

Каким путем пойти — выбирать тебе. Встанешь ли ты на темную сторону или выберешь путь джедая?

Для кого предназначена данная статья

  • Для людей, кто активно пользуется сервисом Google Calendar
  • Для людей, у которых есть файлы таблиц с расписаниями, и которые хотели бы работать с ними более продуктивно — всегда иметь ближайшие мероприятия перед глазами, получать уведомления с напоминаниями на почту или push-уведомления
  • Для людей, которым хочется быстро познакомиться с Google Apps Script, понять, что это такое, и где его можно применить

Что понадобится

  • Google-аккаунт
  • Базовые знания JavaScript

Создаем таблицу в Google Sheets

Это можно сделать, перейдя на страницу своего Google Drive.


Рис. 1. Правый клик мышью по рабочему пространству открывает контекстное меню, где нужно выбрать «Google Таблицы» — «Создать пустую таблицу»

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

  • Название мероприятия
  • Дата мероприятия
  • Время начала
  • Время конца или длительность (впрочем, это необязательно — в итоге вы сможете установить какое-нибудь общее дефолтное значение длительности мероприятия, например 1 час, или вовсе сделать мероприятие, длящимся целый день)

Вся остальная информация, в моем случае — преподаватель, тип занятия (лекция, семинар и т.д.), может быть отправлена в описание мероприятия.


Рис. 2. Таблица с расписанием

Создаем скрипт

2.1. Переходим в редактор скриптов

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


Рис. 3. Скрипт создается, через меню «Инструменты», в котором нужно выбрать пункт «Редактор скриптов»

Скрипты разрабатываются на языке JavaScript. Только что созданный скрипт содержит одну пустую функцию myFunction. Писать код можно в ней, но я предпочту дать ей более осмысленное имя SetCalendar.


Рис. 4. Созданный скрипт, дадим функции осмысленное имя — SetCalendar

Над редактором кода имеется выпадающий список, который позволяет выбрать основную функцию, которая будет являться точкой входа в запущенный скрипт. В нашем случае следуем выбрать функцию SetCalendar.

2.2. Разбираемся с отладкой

Часто для отладки скриптов на JavaScript мы выводим различные значения в консоль:

console.log("Hello, world!")

В Google Apps Script логировать значения следует немного иначе. Вместо объекта console следует использовать глобальный объект Logger:

function SetCalendar() { Logger.log("Hello, world!");}

После запуска скрипта, все выведенные в лог значения можно посмотреть из меню «Вид», выбрав пункт «Журналы» или же по сочетанию клавиш Ctrl+Enter:


Рис. 5. Модальное окно с логами последнего запуска скрипта

2.3. Извлекаем информацию о мероприятиях из таблицы

Сразу небольшой кусок кода

function SetCalendar() { //Индексы первой строки и первого столбца в таблице с данными const rowStart = 1; const colStart = 1; //Количество строк и столбцов в расписании const colsCount = 5; const rowsCount = 67; //Получаем объект страницы var sheet = SpreadsheetApp.getActiveSheet(); //Извлекаем данные таблицы в указанных диапазонах var range = sheet.getRange(rowStart, colStart, rowsCount, colsCount) var data = range.getDisplayValues();}

Рассмотрим строку 11.

Глобальный объект SpreadsheetApp — глобальный объект, содержащий методы, представляющие собой интерфейс для взаимодействия скрипта с приложением Google Sheets.

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

Метод возвращает объект sheet.

Далее, нужно получить объект range — он представляет собой диапазон ячеек таблицы, из которых мы будем извлекать данные. Это делается путем вызова метода getRange. В качестве параметров передаются:

  • Индекс первой строки диапазона
  • Индекс первого столбца диапазона
  • Количество извлекаемых строк
  • Количество извлекаемых столбцов

Обратите внимание! Индексация строк и столбцов начинается с 1.

Строка 15: мы получаем из выбранного выше диапазона ячеек непосредственно данные путем вызова метода getDisplayValues.

Обратите внимание! Метод getDisplayValues() отдает данные в том виде, в каком он отображается в ячейках таблицы. Например, если ячейка содержит дату, то указанный метод вернет не какое-то внутреннее представление записанной в ячейку даты, а ее представление, которое видит пользователь в ячейке.

2.4. Извлекаем данные из ячеек

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

//Константы-имена для индексов столбцов const dateCol = 0; const timeCol = 1; const typeCol = 2; const nameCol = 3; const teacherCol = 4;

Вы можете посмотреть какие столбцы стоят в таблице под данными номерами на рисунке 2.

Объект data представляет набор ячеек с данными, которые были получены. Теперь нужно получить данные из отдельных ячеек.

 for (var i in data) { let row = data; let classDate = row; let classPeriod = row; let classType = row; let className = row; let classTeacher = row; Logger.log(" " + classDate); Logger.log(" " + classPeriod); Logger.log(" " + classType); Logger.log(" " + className); Logger.log(" " + classTeacher); Logger.log("============================================"); }

При использовании цикла for с переменной i по объекту data, переменная i на каждой итерации получает значение — целое число, которое является индексом очередной извлеченной из таблицы строки.

Строка 3: обращение к объекту data по индексу i возвращает объект row — текущую строку таблицы.

Строки 5-9: мы обращаемся к строке по индексу — номеру столбца с интересующими нас данными. Например, row — мы из текущей строки получаем дату мероприятия из столбца с индексом dateCol.

В строках 5-9 мы получили из текущей строки данные о дате занятия, времени начала и окончания, типе занятия, названии дисциплины и имени преподавателя.

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

2.5. Работа с объединенными ячейками

В результате выполнения предыдущих шагов, мы получаем следующий скрипт

function SetCalendar() { //Индексы первой строки и первого столбца в таблице с данными const rowStart = 1; const colStart = 1; //Количество строк и столбцов в расписании const colsCount = 5; const rowsCount = 8; //Получаем объект страницы var sheet = SpreadsheetApp.getActiveSheet(); //Извлекаем данные таблицы в указанных диапазонах var range = sheet.getRange(rowStart, colStart, rowsCount, colsCount) var data = range.getDisplayValues(); //Константы-имена для индексов столбцов const dateCol = 0; const timeCol = 1; const typeCol = 2; const nameCol = 3; const teacherCol = 4; for (var i in data) { let row = data; let classDate = row; let classPeriod = row; let classType = row; let className = row; let classTeacher = row; Logger.log(" " + classDate); Logger.log(" " + classPeriod); Logger.log(" " + classType); Logger.log(" " + className); Logger.log(" " + classTeacher); Logger.log("============================================"); }}

Выполним скрипт и посмотрим, что было выведено в лог:


Рис. 6. Не все даты были правильно извлечены из таблицы

Из лога видно, что из первой строки данные были извлечены, а в следующих строках, которые описывают мероприятия того же дня, поле с датой оказалось пустым. Почему так происходит?

Еще раз взглянем на таблицу:


Рис. 7. Даты вписаны в объединенные ячейки

При создании таблицы, дата вписывалась только в первую строчку расписания на конкретную дату. После чего, все ячейки из столбца, относящиеся к одной дате были объединены.

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

Немного изменим алгоритм, чтобы корректно извлекать дату из объединенных ячеек:

 let savedDate = ""; for (var i in data) { let row = data; let classDate = row; //... if (classDate.trim() == "") { classDate = savedDate; } else { savedDate = classDate; } Logger.log(" " + classDate); //... Logger.log("============================================"); }

Мы вводим дополнительную переменную savedDate — в ней храним последнюю прочитанную дату.

При чтении даты из текущей строки. Если прочитанная дата — пустая строка, то берем дату из переменной savedDate, иначе обновляем значение переменной savedDate.

2.6. Парсим дату и время из строк

Как было отмечено выше — если мы используем метод getDisplayValues(), то значения из таблицы мы получаем в виде строк и ровно так, как они отображаются в ячейках, никаких внутренних представлений данных мы не получаем.

Как будет показано далее, чтобы создать событие в Google Calendar, нужно предварительно создать два объекта класса Date: дату-время начала события, дату-время окончания события.

В рассматриваемой таблице (см. рисунок 2), мы получаем дату занятия в формате dd.mm.yyyy, и временной промежуток, когда занятие длится, в формате hh:mm-hh.mm.

Из этих двух строк, нам нужно получить два объекта Date. Такая задача не касается Google Apps Script, с этим по идее должен справиться хоть сколько-нибудь опытный программист. Поэтому я не буду подробно останавливаться на алгоритме, а лишь приведу код двух JS-функций, которые парсят дату время и возвращают дату-время начала и дату-время окончания занятия.

Поехали:

function extractTime(timeStr, dateStr){ let sepIdx = timeStr.indexOf(":"); let hoursStr = timeStr.substring(0, sepIdx); let minsStr = timeStr.substring(sepIdx + 1); sepIdx = dateStr.indexOf("."); let dayStr = dateStr.substring(0, sepIdx); let monthStr = dateStr.substring(sepIdx + 1, sepIdx + 3); sepIdx = dateStr.indexOf(".", sepIdx + 1); let yearStr = dateStr.substring(sepIdx + 1); let t = new Date(); t.setHours(parseInt(hoursStr), parseInt(minsStr)); t.setYear(parseInt(yearStr)); t.setMonth(parseInt(monthStr) - 1, parseInt(dayStr)); return t;}function extractPeriod(periodStr, dateStr){ let sepIdx = periodStr.indexOf("-"); let fromStr = periodStr.substring(0, sepIdx); let toStr = periodStr.substring(sepIdx + 1); fromStr = fromStr.trim(); toStr = toStr.trim(); return { from: extractTime(fromStr, dateStr), to: extractTime(toStr, dateStr) }}

Чтобы получить дату-время начала и окончания занятия, нужно вызывать функцию extractPeriod и передать две строки — дата и временной период, в который длится занятие.

2.7. Создание события в Google Calendar

Сразу небольшой листинг, пояснения будут дальше

let classTimeInfo = extractPeriod(classPeriod, classDate);let classStartTime = classTimeInfo.from;let classEndTime = classTimeInfo.to;let info = "Преподаватель: " + classTeacher + "\nТип занятия: " + classType;var event = (CalendarApp.getCalendarsByName("Учеба")).createEvent( className, classStartTime, classEndTime, { description: info });Utilities.sleep(50);

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

Далее, к интерфейсу сервиса Google Calendar мы обращаемся через методы глобального объекта CalendarApp.

Посредством метода getCalendarsByName мы получаем массив календарей с указанным именем.

Получить объект-календарь можно также посредством метода getDefaultCalendar, тогда событие будет создано в основном календаре. Однако, я советую зайти в веб-версию Google Calendar и создать новый календарь, в который вы будете экспортировать мероприятия. Во-первых, вы получаете возможность скрывать события этого календаря, выделять события отдельным цветом.

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

В данном случае я создал календарь «Учеба», и собираюсь создавать события в нем. Поэтому в коде вызывается метод getCalendarsByName и в качестве параметра передается срока «Учеба».

Метод возвращает массив календарей с указанным названием, поэтому нужно взять из этого массива один элемент, в нашем случае нулевой:

(CalendarApp.getCalendarsByName("Учеба"))

У объекта-календаря необходимо вызывать метод createEvent. В качестве параметров нужно передать следующее:

  • Название мероприятия (в данном случае название дисциплины)
  • Объект Data — дата-время начала мероприятия
  • Объект Data — дата-время окончания мероприятия
  • Необязательный параметр — объект с дополнительными опциями (в данном случае я заполняю одно поле — description — это описание мероприятия)

Последняя строка в скрипте — задержка на 50 миллисекунд. Во время разработки скрипта и отладки я обнаружил, что иногда при выполнении кода выбрасывается исключение, суть которого в том, что Google Calendar не нравятся частые обращения к сервису, и в описании исключения рекомендуется вставить задержку между вызовами методов API календаря.

Итоговый скрипт

Ниже приведен полный скрипт, который получился

Обратите внимание! Данный скрипт заточен под структуру таблицы, которая приведена на рисунке 2. Если вы хотите использовать этот скрипт для себя как есть, убедитесь, что ваша таблица с расписанием имеет такую же структуру, или же измените скрипт под свою таблицу.

function extractTime(timeStr, dateStr){ let sepIdx = timeStr.indexOf(":"); let hoursStr = timeStr.substring(0, sepIdx); let minsStr = timeStr.substring(sepIdx + 1); sepIdx = dateStr.indexOf("."); let dayStr = dateStr.substring(0, sepIdx); let monthStr = dateStr.substring(sepIdx + 1, sepIdx + 3); sepIdx = dateStr.indexOf(".", sepIdx + 1); let yearStr = dateStr.substring(sepIdx + 1); let t = new Date(); t.setHours(parseInt(hoursStr), parseInt(minsStr)); t.setYear(parseInt(yearStr)); t.setMonth(parseInt(monthStr) - 1, parseInt(dayStr)); return t;}function extractPeriod(periodStr, dateStr){ let sepIdx = periodStr.indexOf("-"); let fromStr = periodStr.substring(0, sepIdx); let toStr = periodStr.substring(sepIdx + 1); fromStr = fromStr.trim(); toStr = toStr.trim(); return { from: extractTime(fromStr, dateStr), to: extractTime(toStr, dateStr) }}function SetCalendar() { //Индексы первой строки и первого столбца в таблице с данными const rowStart = 1; const colStart = 1; //Количество строк и столбцов в расписании const colsCount = 5; const rowsCount = 8; //Получаем объект страницы var sheet = SpreadsheetApp.getActiveSheet(); //Извлекаем данные таблицы в указанных диапазонах var range = sheet.getRange(rowStart, colStart, rowsCount, colsCount) var data = range.getDisplayValues(); //Константы-имена для индексов столбцов const dateCol = 0; const timeCol = 1; const typeCol = 2; const nameCol = 3; const teacherCol = 4; let savedDate = ""; for (var i in data) { let row = data; let classDate = row; let classPeriod = row; let classType = row; let className = row; let classTeacher = row; if (classDate.trim() == "") { classDate = savedDate; } else { savedDate = classDate; } let classTimeInfo = extractPeriod(classPeriod, classDate); let classStartTime = classTimeInfo.from; let classEndTime = classTimeInfo.to; let info = "Преподаватель: " + classTeacher + "\nТип занятия: " + classType; var event = (CalendarApp.getCalendarsByName("Учеба")).createEvent ( className, classStartTime, classEndTime, { description: info } ); Utilities.sleep(50); }}

Немножко скриншотов

Что можно улучшить

  • Некоторые значения в коде я просто захардкодил — например, индексы начала таблицы, размеры таблицы, количество строк (количество мероприятий). Теоретически, можно покопаться в API Google Sheets для Google Apps Script и придумать, как выцеплять нужные координаты ячеек автоматически;
  • Я никогда плотно не изучал JavaScript, могут быть различные стилистические и прочие недостатки в коде, возможно тот же парсинг даты-времени можно сделать красивее и элегантнее.

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

А вы знали, что в Google Apps Script можно использовать HTML страницы со скриптами (javascript) и даже JQuery(с небольшими ограничениями) — совсем как сайты? Сейчас мы покажем простой пример этого!

Итак приступим. Для примера создадим новую таблицу в GoogleSheet. В ней создадим 2 вкладки: test и problems.

На вкладке test сделаем кнопку (для удобства), по которой будет вызваться наш скрипт:

На вкладке problems сделаем таблицу с 3 столбцами — Имя, Браузер, Описание проблемы:

Теперь заходим в редактор скриптов и создаем новую HTML страницу с названием index:

На этой странице делаем простенькую форму с полями для ввода имени, выбора браузера и описания проблемы. И добавляем 2 кнопки — очистить и отправить. На отправить вешаем скрипт на нажатие кнопки — clickButton()

Выглядеть эта форма будет вот так:

Скрипт на нажатие кнопки — clickButton() — выглядит следующим образом:

Принцип действия скрипта очень прост:

Получаем ссылку на Форму и значения полей Имя и Описание проблемы:

var name = form.elements.name.value
var problem = form.elements.problem.value

Проверяем на заполненность поля Имя:

if(!checkFill(name)){
alert('Не заполнено обязательное поле - ИМЯ')
return
}
.............................
function checkFill(objValue){
if(objValue.length > 0){
return true
}
return false
}

Проверяем на заполненность поля Описание проблемы

if(!checkFill(problem)){
alert('Не заполнено обязательное поле - ОПИСАНИЕ ПРОБЛЕМЫ')
return
}
.............................
function checkFill(objValue){
if(objValue.length > 0){
return true
}
return false
}

Получаем наименование Браузера:

И в конце концов запускаем скрипт сервера writeProblem(name, browser, problem) (который будет у нас находится в этом же проекте) с помощью специальной директивы. Так же приписываем 2 callback — они запускаются в зависимости от ответа сервера (нашего скрипта writeProblem). При ошибке запускается выполняется команда .withFailureHandler(onFailure), которая в свою очередь запускает скрипт на клиентской стороне (в этом HTML коде) onFalilure. При правильном завершении скрипта на сервере, запускается команда .withSuccessHandler(onSuccess), которая в свою очередь запускает скрипт на клиентской стороне (в этом HTML коде) onSuccess. В этот скрипт неявно в виде первого аргумента передается возвращаемое значение от скрипта с сервера:

Скрипт onFalilure выводит сообщение, что произошла ошибка:

function onFailure(){
alert('Проблема с записью. Попробуйте еще раз')
}

Скрипт onSuccess проверяет возвращенное значение с сервера, и если это true, то модальное окно с HTML формой закрывается специально директивой:

function onSuccess(test){
if(test){
google.script.host.close()
}
}

Теперь настало время написать серверный скрипт (в нашем случае — это обычный, стандартный Google Apps Script). Переименовываем автоматически созданный скрипт с названием Код на test и создаем 2 функции:

В функции test мы задаем, что из нашего файла index.html мы создаем HTMLOutput и специально командной показываем модальное окно.

В функции writeToProblem мы получаем 3 значения из HTML формы, записываем их на страницу problems и возвращаем true.

Осталось только к нашей картинке назначить скрипт test и можно проверять результаты нашей работы!

Теперь просто нажимаем на нашу кнопку, появляется форма. Заполняем форму, нажимаем отправить — и на нашей вкладке problems появляется новая запись!

Вот такой простой пример использования HTML в GoogleSheets!

Как обычно, ссылка на пример в Google Sheets:

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *