-
-
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#[email protected]'; | |
| 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ххххххх[email protected]
21:17:06 Информация scriptProperties: {toCalendarId=cgtххххххххххххххххххххххх[email protected]}
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ххххххххххх[email protected]}
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 секунд. Но в итоге все добавилось, повторные запуски не пересоздают события, все отрабатывает корректно.