-
-
Save Gvozd/84f9c5ee011fc1344f21ac16db3c58b6 to your computer and use it in GitHub Desktop.
function createTrigger() { | |
ScriptApp.newTrigger('main') | |
.timeBased() | |
.everyDays(1) | |
.create(); | |
} | |
function main() { | |
const {tmp, from, to} = getCalendars(); | |
const syncedEvents = getEvents(to) | |
.map(function(evt) { | |
return { | |
event: evt, | |
eventSeries: run(() => evt.getEventSeries()), | |
fromId: run(() => evt.getDescription()) | |
}; | |
}); | |
getEvents(from).forEach(function(fromEvent) { | |
createEvent(tmp, to, fromEvent, syncedEvents); | |
}); | |
syncedEvents | |
.filter(function({synced}) {return !synced;}) | |
.forEach(function({eventSeries}) { | |
Logger.log('Delete "%s"', run(() => eventSeries.getTitle())); | |
run(() => eventSeries.deleteEventSeries()); | |
}); | |
tmp.deleteCalendar(); | |
deleteTempCals(); | |
} | |
function getEvents(calendar) { | |
const currentYear = new Date().getFullYear(); | |
const fromDate = new Date(currentYear, 0, 1, 1); | |
const toDate = new Date(currentYear + 1, 0, 1, -1); | |
return run(() => calendar.getEvents(fromDate, toDate)); | |
} | |
function createEvent(tmp, cal, evt, syncedEvents) { | |
const evtId = run(() => evt.getEventSeries().getId()) | |
.replace(/^\d{4}_/, '2022_'); | |
const evtTitle = run(() => evt.getTitle()); | |
const evtStartTime = run(() => evt.getAllDayStartDate()); | |
const eventData = syncedEvents.find(function({fromId}) { | |
return fromId === evtId; | |
}) || {}; | |
let {eventSeries, event} = eventData; | |
if( | |
!eventSeries || | |
run(() => eventSeries.getTitle()) !== evtTitle || | |
run(() => event.getStartTime().getTime()) !== run(() => evtStartTime.getTime()) | |
) { | |
Logger.log('%sCreate "%s" %s', eventSeries ? 'Re-' : '', evtTitle, evtStartTime); | |
eventSeries = run(() => tmp.createAllDayEventSeries(evtTitle, evtStartTime, CalendarApp.newRecurrence().addYearlyRule(), { | |
description: evtId | |
})); | |
run( | |
() => eventSeries.setGuestsCanInviteOthers(false) | |
.setGuestsCanModify(false) | |
.setGuestsCanSeeGuests(false) | |
); | |
run(() => | |
Calendar.Events.move(tmp.getId(), eventSeries.getId().split('@')[0], cal.getId()) | |
); | |
} else { | |
Logger.log('Up to date "%s"', evtTitle); | |
eventData.synced = true; | |
} | |
} | |
function getCalendars() { | |
const fromCalendarId = 'addressbook#contacts@group.v.calendar.google.com'; | |
const toCalendarName = 'Birthday Notifications'; | |
const toCalendarIdPropKey = 'toCalendarId'; | |
const scriptProperties = run(() => PropertiesService.getScriptProperties()); | |
Logger.log('scriptProperties: %s', run(() => scriptProperties.getProperties())); | |
let fromCalendar = run(() => CalendarApp.getCalendarById(fromCalendarId)); | |
if (!fromCalendar) { | |
Logger.log('Exported calendar not founded'); | |
return; | |
} | |
let toCalendar = run(() => CalendarApp.getCalendarById(scriptProperties.getProperty(toCalendarIdPropKey))); | |
// Этот календарь нужен дял того чтобы подхватывались дефолтовые напоминания из конечного календаря | |
// у Calendar API куча проблем с заданием напоминаний для all-day событий, особенно в дату самого события(а не до) | |
// Эти события при попытке resetRemindersToDefault() получают напоминания от обычных событий, а не полнодневных. | |
// И в итоге не в календаре не удается задать полнодневное напоминание, и уж тем более в день самого ДР. Только обычные напоминания, и только заранее | |
// Но я обнаружил ХАК. Если созданное через API полнодневное событие передвинуть в другой календарь - оно подхватит его дефолтовые полнодневные напоминания | |
// К сожалению при возвращении в исходный календарь - все равно возвращается итоговая бага | |
// Поэтому создаем событие во временном календаре, а потом двигаем в основной. Все буде ОК | |
// При изменении напоминаний в конечном календаре - они подхвататся для всех событий, и это то, что нужно! | |
let tmpCalendar = run(() => CalendarApp.createCalendar('Temp calendar').setHidden(true)); | |
if (!toCalendar) { | |
Logger.log('Import calendar not founded - create it'); | |
toCalendar = run(() => CalendarApp.createCalendar(toCalendarName)); | |
run(() => scriptProperties.setProperty(toCalendarIdPropKey, toCalendar.getId())); | |
} | |
return {tmp: tmpCalendar, from: fromCalendar, to: toCalendar}; | |
} | |
function run(func) { | |
const timeouts = [10, 50, 100, 250, 500, 1000, 2000]; | |
let error; | |
for(const timeout of timeouts) { | |
let start, end; | |
try { | |
start = Date.now(); | |
return func(); | |
} catch(e) { | |
error = e; | |
} finally { | |
end = Date.now(); | |
Utilities.sleep( | |
Math.max(0, timeout - (end - start)) | |
); | |
} | |
} | |
throw error; | |
} | |
// @author Winand | |
function deleteTempCals() { | |
// Удаление календарей Temp calendar | |
// CalendarApp.getAllOwnedCalendars() не возвращает скрытые календари, | |
// поэтому используется Calendar.CalendarList https://stackoverflow.com/a/32384340 | |
var cals = Calendar.CalendarList.list( | |
{showHidden:true, minAccessRole:'owner', fields:'items(id,summary)'} | |
).items; | |
for(const i of cals) { | |
if(i.summary == 'Temp calendar') { | |
Logger.log('del cal ' + i.id); | |
// Calendar.Calendars.remove(i.id); | |
CalendarApp.getOwnedCalendarById(i.id).deleteCalendar(); | |
} | |
} | |
} |
@Gvozd
Добрый день!
Тоже некоторое время назад перестали обновляться календари, у тригера 100% ошибок.
Удалил наплодившиеся временные календари, запустил вручную main. Он почему-то очень медленно обрабатывал контакты: в среднем 1 контакт за 20 секунд. В итоге за 6 минут - 18 контактов.
Попытки уменьшить таймаут к результату не привели, вылетает ошибка по таймауту.
Попробовал глубокой ночью запустить руками - результат тот же.
А ограничение на работу в 6 минут установлено гуглом? Почему скрипт не дорабатывает до конца, пусть и с большими задержками.
@KirillChernobrivchenko не знаю почему у вас так долго выполняется
у меня 130 записей обрабатывается за минуту-две
вы используете последнюю версию скрипта?
у вас только один активный тригер?
6 минут - это встроенное ограничение гугла, оно не настраивается
У меня тоже наплодились календари и ничего не работает. Случайно увидел, что сам же писал скрипт для их удаления.)) Большое облегчение
17:50:31 Примечание Выполнение начато
17:50:36 Информация scriptProperties: {toCalendarId=vs3qkisvg6olo6epbk98bdm4lo@group.calendar.google.com}
17:53:50 Информация Re-Create "***" Mon Feb 28 00:00:00 GMT+03:00 2022
17:54:12 Информация Re-Create "***" Tue Mar 01 00:00:00 GMT+03:00 2022
17:54:31 Информация Re-Create "***" Fri Mar 04 00:00:00 GMT+03:00 2022
17:54:52 Информация Re-Create "***" Sat Mar 26 00:00:00 GMT+03:00 2022
17:55:11 Информация Re-Create "***" Thu Apr 07 00:00:00 GMT+03:00 2022
17:55:29 Информация Re-Create "***" Sat Apr 16 00:00:00 GMT+03:00 2022
17:55:47 Информация Re-Create "***" Sat Apr 16 00:00:00 GMT+03:00 2022
17:56:04 Информация Re-Create "***" Tue Apr 19 00:00:00 GMT+03:00 2022
17:56:19 Информация Re-Create "***" Tue Apr 26 00:00:00 GMT+03:00 2022
17:56:31 Ошибка
Exceeded maximum execution time
@Gvozd скрипт последней версии, да. Специально обновлял, на всякий случай, позавчера. Триггер один.
Такое ощущение, что на аккаунте дневную квоту по созданию событий порезали до 0...
Вы территориально где живете? Может это в России квоты порезали
@Winand Вашим скриптом для чистки временных календарей и пользуюсь, спасибо)))
И процесс у меня ровно такой же, как у Вас: тоже примерно раз в 20 секунд напоминание по контакту создается.
Re-Create на каждой записи вероятнее всего означает что где-то сбито время
Потому что в штатном режиме он должен вызываться только при изменении имени или дня рождения в исходном контакте
- Проверьте что в созданном календаре "Birthday Notifications" такой же часовой пояс, что и в стандартном календаре "Дни рождения"
если нет, то поменяйте у созданного календаря
у встроенного календаря часовой пояс не настраивается - наверно берется из вашего гугл-профиля - затем в настройках самого скрипта также замените часовой пояс на такой же
также, так как скрипт падал по таймауту, то он только добавлял несколько записей, а до процедуры очистки лишних записей дело не доходило
есть подозрение что ваш "Birthday Notifications" переполнен дубликатами, и по этой причине может быть медленно добавляются новые записи
попробуйте удалить его, запустить скрипт, и посмотреть что будет
потом проверьте настройки часового пояса в новом календаре, и если они неверные, поправьте и запустите скрипт еще раз
Установка часового пояса, соответствующего основному календарю, помогла. Изначально стояло время GMT+0.
Но скрипт всё равно не успел за 6 минут отработать, поскольку после сообщений "up to date" он стал удалять по ~50 раз каждое уведомление. Возможно, это последствия неудачных запусков ранее.
UPD: Второй запуск отработал уже успешно.
@Gvozd Спасибо большое!
В календаре "Birthday Notifications" почему-то действительно был часовой пояс GMT+00:00, хотя в остальных календарях аккаунта GMT+03:00. Где именно в скрипте настраивается часовой пояс, я сходу не понял. В меню "Настройки проекта" указан корректный GMT+03:00, тем не менее "Birthday Notifications" создается по Гринвичу
В итоге удалил календарь "Birthday Notifications", запустил main, после создания календаря руками поменял часовой пояс.
Потребовалось несколько запусков, чтобы добавить все ДР, т.к. добавление все равно длится 15-20 секунд. Но в итоге все добавилось, повторные запуски не пересоздают события, все отрабатывает корректно.
Новогоднее обновление
У исходных событий в getId()
участвовал текущий год
Это привело к пересозданию событий в целевом календаре
В случае большого количества контактов скрипт не укладывался в таймаут, и поэтому не доходил до очистки старых событий, и не удалял за собой временные календари
- сделал замену в getId на 2022-ой год
таким образом при следующем запуске он только почистит новые дубликаты, а старые события останутся
в таймаут скрипт должен укладываться - добавил в конец вызов deleteTempCals за авторством @Winand, чтобы почистить временные календари
С праздниками!
Попробовал новый скрипт.
Первый запуск не избавил от дубликатов и не удаленных временных календарей, остановился так же по таймауту 360сек.
Запустил функцию удаление временных календарей. Пришлось несколько раз запускать... :) так их много у меня оказалось.
Удалил основной календарь, т.к. там было по 20+ дубликатов.
Запустил main после создания основного календаря остановил скрипт и поменял часовой пояс на свой.
Запустил main повторно он остановился отработав январь и не удалив за собой временный календарь и осталась пара дубликатов начала января, видимо те, что до смены часового пояса затясались. Добавил строчку с функцией от @Winand с удалением временных календарей в начало main'a, чтоб сначала удалялись временные календари, а только потом создавались новые.
Повторные запуски отрабатывали с каждым разом всё меньше, но не создавали дубликатов в основном и не плодились временные календари. Сейчас остановился на ноябре и дальше не продвигается т.к. почти всё время процесса занимает scriptProperties
21:17:02 Примечание Выполнение начато
21:17:03 Информация del cal l7sk8ndlххххххх069b404@group.calendar.google.com
21:17:06 Информация scriptProperties: {toCalendarId=cgtххххххххххххххххххххххх00@group.calendar.google.com}
21:22:03 Информация Up to date "Алексей – день рождения"
21:22:03 Информация Up to date " – Антон "
......
21:23:00 Информация Up to date "Даниил – день рождения"
21:23:00 Информация Create "Екатерина – день рождения" Tue Nov 14 00:00:00 GMT+03:00 2023
21:23:02 Ошибка
Exceeded maximum execution time
Всегда заканчивается на Екатерине и она не добавляется по итогу.
upd:
Скрипт перестал вообще отрабатывать вот что пишет:
00:46:05 Примечание Выполнение начато
00:46:06 Информация scriptProperties: {toCalendarId=cgххххххххххх0@group.calendar.google.com}
00:46:06 Информация Exported calendar not founded
00:46:07 Ошибка TypeError: Cannot destructure property 'tmp' of 'getCalendars(...)' as it is undefined.
main @ Notifications.gs:10
У меня 10-ая строчка это:
8 function main() {
9 deleteTempCals();
10 const {tmp, from, to} = getCalendars();
11 const syncedEvents = getEvents(to)
Добрый день.
Ситуация абсолютно аналогичная @armen1313, только последней ошибки нет, скрипт запускается. Думал я один такой с сумасшедшим количеством записей в книжке.
Перенести удаление в начало хорошая идея. Я же сделал отдельный триггер для удаления.
Выручайте.
@armen1313 такая ошибка может быть, если не сделать эту часть инструкции
https://gist.github.com/Gvozd/84f9c5ee011fc1344f21ac16db3c58b6?permalink_comment_id=3747641#gistcomment-3747641
Недавно Гугл стал сам предлагать включать уведомления о днях рождения. Но, как я понимаю, индивидуально для каждого человека? Это не особо удобно
@SVK050 я обновляю этот gist, когда вношу изменения в скрипт
вы видите последнюю версию скрипта
@armen1313 у меня сейчас нет идей как это исправить наверняка
можете попробовать уменьшить в 109-ой строчке значения таймаутов, начиная с первых
Эти таймауты используются когда GoogleAPI отклоняет вызовы по слишком частому обращению, и возможно они завышены
Они были подобраны мной примерно наугад, для того, чтобы гарантировано получить все-таки значение при повторных попытках