重要提示:此快速入门指南仅适用于网络会议提供商。
以下 Google Workspace 加购项快速入门指南将介绍如何扩展 Google 日历,使其与名为“My Web Conferencing”的虚构网络会议服务同步。在修改日历活动时,用户可以通过该插件看到我的网络会议作为会议选项。
此快速入门演示了会议创建和活动同步,但在您将其连接到会议解决方案 API 之前,它无法正常运行。
目标
- 设置环境。
- 设置脚本。
- 运行脚本。
前提条件
- 可访问互联网的网络浏览器。
- Google Workspace 账号(您可能需要获得管理员批准)。
- 具有一个 Google Cloud 项目。
设置环境
在 Google Cloud 控制台中打开您的 Cloud 项目
如果尚未打开,请打开您打算用于此示例的 Cloud 项目:
- 在 Google Cloud 控制台中,前往选择项目页面。
- 选择您要使用的 Google Cloud 项目。或者,点击创建项目,然后按照屏幕上的说明操作。如果您创建了 Google Cloud 项目,可能需要为该项目启用结算功能。
启用 Calendar API
本快速入门使用日历高级服务,该服务可访问日历 API。
在使用 Google API 之前,您需要在 Google Cloud 项目中将其开启。 您可以在单个 Google Cloud 项目中启用一个或多个 API。在您的 Google Cloud 项目中,开启 Calendar API。
配置 OAuth 权限请求页面
Google Workspace 加购项需要配置同意屏幕。配置 Google Workspace 加载项的 OAuth 权限请求页面可定义 Google 向用户显示的内容。
- 在 Google Cloud 控制台中,依次前往菜单 > Google Auth platform > 品牌推广。
- 如果您已配置 Google Auth platform,则可以在品牌推广、受众群体和数据访问中配置以下 OAuth 权限请求页面设置。如果您看到一条消息,指出Google Auth platform 尚未配置,请点击开始:
- 在应用信息下,在应用名称中输入应用的名称。
- 在用户支持电子邮件中,选择一个支持电子邮件地址,以便用户在对自己的同意情况有疑问时与您联系。
- 点击下一步。
- 在受众群体下,选择内部。
- 点击下一步。
- 在联系信息下,输入一个电子邮件地址,以便您接收有关项目变更的通知。
- 点击下一步。
- 在完成部分,查看 Google API 服务用户数据政策,如果您同意该政策,请选择我同意《Google API 服务:用户数据政策》。
- 点击继续。
- 点击创建。
- 目前,您可以跳过添加范围的步骤。 未来,如果您创建的应用供 Google Workspace 组织以外的用户使用,则必须将用户类型更改为外部。然后,添加应用所需的授权范围。如需了解详情,请参阅完整的配置 OAuth 同意指南。
设置脚本
创建 Apps 脚本项目
- 如需创建新的 Apps 脚本项目,请前往 script.new。
- 点击无标题项目。
- 将 Apps 脚本项目重命名为 Conference add-on,然后点击重命名。
- 在
Code.gs
文件旁边,依次点击“更多”图标 > 重命名。将文件命名为CreateConf
。 - 依次点击“添加文件”图标 > 脚本。
- 将文件命名为
Syncing
。 将每个文件的内容替换为以下相应代码:
CreateConf.gs
/** * Creates a conference, then builds and returns a ConferenceData object * with the corresponding conference information. This method is called * when a user selects a conference solution defined by the add-on that * uses this function as its 'onCreateFunction' in the add-on manifest. * * @param {Object} arg The default argument passed to a 'onCreateFunction'; * it carries information about the Google Calendar event. * @return {ConferenceData} */ function createConference(arg) { const eventData = arg.eventData; const calendarId = eventData.calendarId; const eventId = eventData.eventId; // Retrieve the Calendar event information using the Calendar // Advanced service. var calendarEvent; try { calendarEvent = Calendar.Events.get(calendarId, eventId); } catch (err) { // The calendar event does not exist just yet; just proceed with the // given event ID and allow the event details to sync later. console.log(err); calendarEvent = { id: eventId, }; } // Create a conference on the third-party service and return the // conference data or errors in a custom JSON object. var conferenceInfo = create3rdPartyConference(calendarEvent); // Build and return a ConferenceData object, either with conference or // error information. var dataBuilder = ConferenceDataService.newConferenceDataBuilder(); if (!conferenceInfo.error) { // No error, so build the ConferenceData object from the // returned conference info. var phoneEntryPoint = ConferenceDataService.newEntryPoint() .setEntryPointType(ConferenceDataService.EntryPointType.PHONE) .setUri('tel:+' + conferenceInfo.phoneNumber) .setPin(conferenceInfo.phonePin); var adminEmailParameter = ConferenceDataService.newConferenceParameter() .setKey('adminEmail') .setValue(conferenceInfo.adminEmail); dataBuilder.setConferenceId(conferenceInfo.id) .addEntryPoint(phoneEntryPoint) .addConferenceParameter(adminEmailParameter) .setNotes(conferenceInfo.conferenceLegalNotice); if (conferenceInfo.videoUri) { var videoEntryPoint = ConferenceDataService.newEntryPoint() .setEntryPointType(ConferenceDataService.EntryPointType.VIDEO) .setUri(conferenceInfo.videoUri) .setPasscode(conferenceInfo.videoPasscode); dataBuilder.addEntryPoint(videoEntryPoint); } // Since the conference creation request succeeded, make sure that // syncing has been enabled. initializeSyncing(calendarId, eventId, conferenceInfo.id); } else if (conferenceInfo.error === 'AUTH') { // Authenentication error. Implement a function to build the correct // authenication URL for the third-party conferencing system. var authenticationUrl = getAuthenticationUrl(); var error = ConferenceDataService.newConferenceError() .setConferenceErrorType( ConferenceDataService.ConferenceErrorType.AUTHENTICATION) .setAuthenticationUrl(authenticationUrl); dataBuilder.setError(error); } else { // Other error type; var error = ConferenceDataService.newConferenceError() .setConferenceErrorType( ConferenceDataService.ConferenceErrorType.TEMPORARY); dataBuilder.setError(error); } // Don't forget to build the ConferenceData object. return dataBuilder.build(); } /** * Contact the third-party conferencing system to create a conference there, * using the provided calendar event information. Collects and retuns the * conference data returned by the third-party system in a custom JSON object * with the following fields: * * data.adminEmail - the conference administrator's email * data.conferenceLegalNotice - the conference legal notice text * data.error - Only present if there was an error during * conference creation. Equal to 'AUTH' if the add-on user needs to * authorize on the third-party system. * data.id - the conference ID * data.phoneNumber - the conference phone entry point phone number * data.phonePin - the conference phone entry point PIN * data.videoPasscode - the conference video entry point passcode * data.videoUri - the conference video entry point URI * * The above fields are specific to this example; which conference information * your add-on needs is dependent on the third-party conferencing system * requirements. * * @param {Object} calendarEvent A Calendar Event resource object returned by * the Google Calendar API. * @return {Object} */ function create3rdPartyConference(calendarEvent) { var data = {}; // Implementation details dependent on the third-party system API. // Typically one or more API calls are made to create the conference and // acquire its relevant data, which is then put in to the returned JSON // object. return data; } /** * Return the URL used to authenticate the user with the third-party * conferencing system. * * @return {String} */ function getAuthenticationUrl() { var url; // Implementation details dependent on the third-party system. return url; }
Syncing.gs
/** * 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. } }
点击项目设置
。
选中在编辑器中显示“appsscript.json”清单文件复选框。
点击编辑器
。打开
appsscript.json
文件,并将其内容替换为以下代码,然后点击“保存”图标。
appsscript.json
{ "addOns": { "calendar": { "conferenceSolution": [{ "id": 1, "name": "My Web Conference", "logoUrl": "https://lh3.googleusercontent.com/...", "onCreateFunction": "createConference" }], "currentEventAccess": "READ_WRITE" }, "common": { "homepageTrigger": { "enabled": false }, "logoUrl": "https://lh3.googleusercontent.com/...", "name": "My Web Conferencing" } }, "timeZone": "America/New_York", "dependencies": { "enabledAdvancedServices": [ { "userSymbol": "Calendar", "serviceId": "calendar", "version": "v3" } ] }, "webapp": { "access": "ANYONE", "executeAs": "USER_ACCESSING" }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/calendar.addons.current.event.read", "https://www.googleapis.com/auth/calendar.addons.current.event.write", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/script.scriptapp" ] }
复制 Cloud 项目编号
- 在 Google Cloud 控制台中,依次前往“菜单”图标 > IAM 和管理 > 设置。
- 在项目编号字段中,复制相应值。
设置 Apps 脚本项目的 Cloud 项目
- 在您的 Apps 脚本项目中,点击项目设置
。
- 在 Google Cloud Platform (GCP) 项目下,点击更改项目。
- 在 GCP project number 中,粘贴 Google Cloud 项目编号。
- 点击设置项目。
安装测试部署
- 在您的 Apps 脚本项目中,点击编辑器图标 。
- 打开
CreateConf.gs
文件,然后点击运行。根据提示为脚本授权。 - 依次点击部署 > 测试部署。
- 依次点击安装 > 完成。
运行脚本
- 前往 calendar.google.com。
- 创建新活动或打开某个现有活动。
- 点击添加 Google Meet 视频会议旁边的下拉箭头 > 我的网络会议。由于该插件未连接到真正的第三方会议解决方案,因此系统会显示无法创建会议错误。