用户可以自由更新或删除其 Google 日历活动。如果用户 在创建会议后更新了活动,您的插件可能需要 通过更新会议数据来响应更改。如果您的 第三方会议系统依赖于对活动数据的跟踪 如果活动更改时未能更新会议,则会显示会议 会导致糟糕的用户体验。
根据 Google 日历活动称为同步。您可以通过创建一个 Apps Script 可安装的触发器来同步活动更改,该触发器会在给定日历中的活动发生更改时触发。很抱歉,该触发器不会报告哪些事件发生了更改,并且您无法将其限制为仅针对包含您创建的会议的事件。而是必须请求一个 自上次同步以来对日历所做的所有更改, 并相应地进行更新
常规同步过程如下:
- 用户首次创建会议时,系统会初始化同步流程。
- 每当用户创建、更新或删除某个日历活动时 触发器在插件项目中执行触发器函数。
- 触发器函数会检查上次同步后发生的一组事件更改,并确定是否需要更新关联的第三方会议。
- 系统会通过发出第三方 API 请求来对会议进行任何必要的更新。
- 系统会存储新的同步令牌,以便下次触发器执行时只需检查日历的最新更改。
初始化同步
当该插件在第三方系统上成功创建会议后, 系统应创建一个可安装的触发器 可响应 事件更改 此日历中(如果该触发器尚不存在)。
创建触发器后,应通过创建初始 同步令牌。具体方法是直接执行触发器函数。
创建日历触发器
如要进行同步,您的插件需要检测包含附加
会议已更改。这可通过创建 EventUpdated
可安装的触发器来实现。您的插件只需为每个日历创建一个触发器,并且可以通过编程方式创建这些触发器。
创建触发器的理想时机是在用户创建第一场会议、 因为这时候用户就开始使用相应插件了创建会议并验证没有错误后,您的插件应检查此用户是否存在触发器,如果没有,则创建触发器。
实现同步触发器函数
当 Apps Script 检测到导致触发器触发的条件时,就会执行触发器函数。当用户在指定日历中创建、修改或删除任何活动时,EventUpdated
日历触发器会触发。
您必须实现插件使用的触发器函数。此触发器函数应执行以下操作:
- 使用
syncToken
调用日历高级服务Calendar.Events.list()
,以检索自上次同步后发生更改的活动列表。通过使用同步令牌,您可以减少您的插件必须检查的事件数量。如果触发器函数在执行时没有有效的同步令牌,则会回退到完整同步。完整同步只是尝试检索规定时间范围内的所有事件,以生成新的有效同步令牌。
- 系统会检查每个修改后的事件,以确定它是否具有 第三方会议
- 如果活动有会议,系统会对其进行检查,了解发生了哪些变化。根据具体更改,您可能需要修改关联的会议。例如,如果一个活动被删除了 可能也会删除会议
- 如需对会议进行必要的更改,请通过调用 API 来 第三方系统
- 完成所有必要的更改后,存储由
nextSyncToken
Calendar.Events.list()
方法。此同步令牌位于Calendar.Events.list()
调用返回的最后一页结果中。
更新 Google 日历活动
在某些情况下,您可能希望在同步时更新 Google 日历活动。如果您选择这样做,请使用适当的 Google 日历高级服务请求进行活动更新。请务必使用
条件更新
带有 If-Match
标头。这样可以防止您的更改无意中覆盖用户在其他客户端中进行的并发更改。
示例
以下示例展示了如何设置日历活动的同步 及其关联的会议
/** * Initializes syncing of conference data by creating a sync trigger and * sync token if either does not exist yet. * * @param {String} calendarId The ID of the Google Calendar. */ function initializeSyncing(calendarId) { // Create a syncing trigger if it doesn't exist yet. createSyncTrigger(calendarId); // Perform an event sync to create the initial sync token. syncEvents({'calendarId': calendarId}); } /** * Creates a sync trigger if it does not exist yet. * * @param {String} calendarId The ID of the Google Calendar. */ function createSyncTrigger(calendarId) { // Check to see if the trigger already exists; if does, return. var allTriggers = ScriptApp.getProjectTriggers(); for (var i = 0; i < allTriggers.length; i++) { var trigger = allTriggers[i]; if (trigger.getTriggerSourceId() == calendarId) { return; } } // Trigger does not exist, so create it. The trigger calls the // 'syncEvents()' trigger function when it fires. var trigger = ScriptApp.newTrigger('syncEvents') .forUserCalendar(calendarId) .onEventUpdated() .create(); } /** * Sync events for the given calendar; this is the syncing trigger * function. If a sync token already exists, this retrieves all events * that have been modified since the last sync, then checks each to see * if an associated conference needs to be updated and makes any required * changes. If the sync token does not exist or is invalid, this * retrieves future events modified in the last 24 hours instead. In * either case, a new sync token is created and stored. * * @param {Object} e If called by a event updated trigger, this object * contains the Google Calendar ID, authorization mode, and * calling trigger ID. Only the calendar ID is actually used here, * however. */ function syncEvents(e) { var calendarId = e.calendarId; var properties = PropertiesService.getUserProperties(); var syncToken = properties.getProperty('syncToken'); var options; if (syncToken) { // There's an existing sync token, so configure the following event // retrieval request to only get events that have been modified // since the last sync. options = { syncToken: syncToken }; } else { // No sync token, so configure to do a 'full' sync instead. In this // example only recently updated events are retrieved in a full sync. // A larger time window can be examined during a full sync, but this // slows down the script execution. Consider the trade-offs while // designing your add-on. var now = new Date(); var yesterday = new Date(); yesterday.setDate(now.getDate() - 1); options = { timeMin: now.toISOString(), // Events that start after now... updatedMin: yesterday.toISOString(), // ...and were modified recently maxResults: 50, // Max. number of results per page of responses orderBy: 'updated' } } // Examine the list of updated events since last sync (or all events // modified after yesterday if the sync token is missing or invalid), and // update any associated conferences as required. var events; var pageToken; do { try { options.pageToken = pageToken; events = Calendar.Events.list(calendarId, options); } catch (err) { // Check to see if the sync token was invalidated by the server; // if so, perform a full sync instead. if (err.message === "Sync token is no longer valid, a full sync is required.") { properties.deleteProperty('syncToken'); syncEvents(e); return; } else { throw new Error(err.message); } } // Read through the list of returned events looking for conferences // to update. if (events.items && events.items.length > 0) { for (var i = 0; i < events.items.length; i++) { var calEvent = events.items[i]; // Check to see if there is a record of this event has a // conference that needs updating. if (eventHasConference(calEvent)) { updateConference(calEvent, calEvent.conferenceData.conferenceId); } } } pageToken = events.nextPageToken; } while (pageToken); // Record the new sync token. if (events.nextSyncToken) { properties.setProperty('syncToken', events.nextSyncToken); } } /** * Returns true if the specified event has an associated conference * of the type managed by this add-on; retuns false otherwise. * * @param {Object} calEvent The Google Calendar event object, as defined by * the Calendar API. * @return {boolean} */ function eventHasConference(calEvent) { var name = calEvent.conferenceData.conferenceSolution.name || null; // This version checks if the conference data solution name matches the // one of the solution names used by the add-on. Alternatively you could // check the solution's entry point URIs or other solution-specific // information. if (name) { if (name === "My Web Conference" || name === "My Recorded Web Conference") { return true; } } return false; } /** * Update a conference based on new Google Calendar event information. * The exact implementation of this function is highly dependant on the * details of the third-party conferencing system, so only a rough outline * is shown here. * * @param {Object} calEvent The Google Calendar event object, as defined by * the Calendar API. * @param {String} conferenceId The ID used to identify the conference on * the third-party conferencing system. */ function updateConference(calEvent, conferenceId) { // Check edge case: the event was cancelled if (calEvent.status === 'cancelled' || eventHasConference(calEvent)) { // Use the third-party API to delete the conference too. } else { // Extract any necessary information from the event object, then // make the appropriate third-party API requests to update the // conference with that information. } }