-
-
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(); | |
} | |
} | |
} |
Новогоднее обновление
У исходных событий в 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
Недавно Гугл стал сам предлагать включать уведомления о днях рождения. Но, как я понимаю, индивидуально для каждого человека? Это не особо удобно
@Gvozd Спасибо большое!
В календаре "Birthday Notifications" почему-то действительно был часовой пояс GMT+00:00, хотя в остальных календарях аккаунта GMT+03:00. Где именно в скрипте настраивается часовой пояс, я сходу не понял. В меню "Настройки проекта" указан корректный GMT+03:00, тем не менее "Birthday Notifications" создается по Гринвичу
В итоге удалил календарь "Birthday Notifications", запустил main, после создания календаря руками поменял часовой пояс.
Потребовалось несколько запусков, чтобы добавить все ДР, т.к. добавление все равно длится 15-20 секунд. Но в итоге все добавилось, повторные запуски не пересоздают события, все отрабатывает корректно.