制定会议议程

编码级别:初级
时长:15 分钟
项目类型:使用事件驱动型触发器的自动化操作

目标

  • 了解该解决方案的用途。
  • 了解 Apps Script 服务在解决方案中的作用。
  • 设置脚本。
  • 运行脚本。

关于此解决方案

自动在 Google 文档中创建议程文档,并将其附加到 Google 日历会议。

添加到日历活动的日程的屏幕截图

工作原理

该脚本会为议程创建文档模板。当您更新日历时,该脚本会检查您拥有的任何活动是否在说明中包含“#agenda”。如果存在该标记,脚本会复制模板,将其添加到日历活动中,并与活动参加者共享。

Apps Script 服务

此解决方案使用以下服务:

  • 云端硬盘服务 - 检查模板文档是否存在,如果不存在,则为模板文档创建一个新文件夹。为每个新议程创建模板文档的副本。
  • 文档服务 - 创建议程模板。
  • 日历服务 - 检查是否包含“#agenda”标记的活动,并使用指向日程文档的链接更新活动说明。
  • 基本服务 - 使用 Session 类获取用户的电子邮件地址。这有助于为当前用户构建触发器。
  • 脚本服务 - 创建一个触发器,以便在用户的日历发生更改时触发。

前提条件

如需使用此示例,您需要满足以下前提条件:

  • Google 账号(Google Workspace 账号可能需要管理员批准)。
  • 一个能够访问互联网的网络浏览器。

设置脚本

  1. 点击下方的按钮,打开制作会议议程示例 Apps 脚本项目。
    打开项目
  2. 点击概览
  3. 在概览页面上,点击“复制”图标 用于创建副本的图标
  4. 在复制的项目中,从函数下拉菜单中选择 setUp
  5. 点击运行
  6. 根据提示为脚本授权。如果 OAuth 意见征求界面显示此应用未经验证警告,请依次选择高级 > 前往 {Project Name}(不安全)以继续操作。

运行脚本

  1. 打开 Google 日历
  2. 创建新活动或修改现有活动。
  3. 在说明中添加 #agenda,然后保存活动。
  4. 查看您的电子邮件,看看是否有关于有人与您共享了文档的电子邮件通知,或者刷新日历并再次点击相应活动,查看与议程文档相关的链接。

所有参加者都会收到电子邮件通知,以便查看会议议程。该脚本会向与会者授予编辑权限,但您可以修改该脚本,以更新与会者的议程文档权限

查看代码

如需查看此解决方案的 Apps 脚本代码,请点击下方的查看源代码

查看源代码

Code.gs

solutions/automations/agenda-maker/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/apps-script/samples/automations/agenda-maker

/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Checks if the folder for Agenda docs exists, and creates it if it doesn't.
 *
 * @return {*} Drive folder ID for the app.
 */
function checkFolder() {
  const folders = DriveApp.getFoldersByName('Agenda Maker - App');
  // Finds the folder if it exists
  while (folders.hasNext()) {
    let folder = folders.next();
    if (
      folder.getDescription() ==
        'Apps Script App - Do not change this description' &&
      folder.getOwner().getEmail() == Session.getActiveUser().getEmail()
    ) {
      return folder.getId();
    }
  }
  // If the folder doesn't exist, creates one
  let folder = DriveApp.createFolder('Agenda Maker - App');
  folder.setDescription('Apps Script App - Do not change this description');
  return folder.getId();
}

/**
 * Finds the template agenda doc, or creates one if it doesn't exist.
 */
function getTemplateId(folderId) {
  const folder = DriveApp.getFolderById(folderId);
  const files = folder.getFilesByName('Agenda TEMPLATE##');

  // If there is a file, returns the ID.
  while (files.hasNext()) {
    const file = files.next();
    return file.getId();
  }

  // Otherwise, creates the agenda template.
  // You can adjust the default template here
  const doc = DocumentApp.create('Agenda TEMPLATE##');
  const body = doc.getBody();

  body
      .appendParagraph('##Attendees##')
      .setHeading(DocumentApp.ParagraphHeading.HEADING1)
      .editAsText()
      .setBold(true);
  body.appendParagraph(' ').editAsText().setBold(false);

  body
      .appendParagraph('Overview')
      .setHeading(DocumentApp.ParagraphHeading.HEADING1)
      .editAsText()
      .setBold(true);
  body.appendParagraph(' ');
  body.appendParagraph('- Topic 1: ').editAsText().setBold(true);
  body.appendParagraph(' ').editAsText().setBold(false);
  body.appendParagraph('- Topic 2: ').editAsText().setBold(true);
  body.appendParagraph(' ').editAsText().setBold(false);
  body.appendParagraph('- Topic 3: ').editAsText().setBold(true);
  body.appendParagraph(' ').editAsText().setBold(false);

  body
      .appendParagraph('Next Steps')
      .setHeading(DocumentApp.ParagraphHeading.HEADING1)
      .editAsText()
      .setBold(true);
  body.appendParagraph('- Takeaway 1: ').editAsText().setBold(true);
  body.appendParagraph('- Responsible: ').editAsText().setBold(false);
  body.appendParagraph('- Accountable: ');
  body.appendParagraph('- Consult: ');
  body.appendParagraph('- Inform: ');
  body.appendParagraph(' ');
  body.appendParagraph('- Takeaway 2: ').editAsText().setBold(true);
  body.appendParagraph('- Responsible: ').editAsText().setBold(false);
  body.appendParagraph('- Accountable: ');
  body.appendParagraph('- Consult: ');
  body.appendParagraph('- Inform: ');
  body.appendParagraph(' ');
  body.appendParagraph('- Takeaway 3: ').editAsText().setBold(true);
  body.appendParagraph('- Responsible: ').editAsText().setBold(false);
  body.appendParagraph('- Accountable: ');
  body.appendParagraph('- Consult: ');
  body.appendParagraph('- Inform: ');

  doc.saveAndClose();

  folder.addFile(DriveApp.getFileById(doc.getId()));

  return doc.getId();
}

/**
 * When there is a change to the calendar, searches for events that include "#agenda"
 * in the decrisption.
 *
 */
function onCalendarChange() {
  // Gets recent events with the #agenda tag
  const now = new Date();
  const events = CalendarApp.getEvents(
      now,
      new Date(now.getTime() + 2 * 60 * 60 * 1000000),
      {search: '#agenda'},
  );

  const folderId = checkFolder();
  const templateId = getTemplateId(folderId);

  const folder = DriveApp.getFolderById(folderId);

  // Loops through any events found
  for (i = 0; i < events.length; i++) {
    const event = events[i];

    // Confirms whether the event has the #agenda tag
    let description = event.getDescription();
    if (description.search('#agenda') == -1) continue;

    // Only works with events created by the owner of this calendar
    if (event.isOwnedByMe()) {
      // Creates a new document from the template for an agenda for this event
      const newDoc = DriveApp.getFileById(templateId).makeCopy();
      newDoc.setName('Agenda for ' + event.getTitle());

      const file = DriveApp.getFileById(newDoc.getId());
      folder.addFile(file);

      const doc = DocumentApp.openById(newDoc.getId());
      const body = doc.getBody();

      // Fills in the template with information about the attendees from the
      // calendar event
      const conf = body.findText('##Attendees##');
      if (conf) {
        const ref = conf.getStartOffset();

        for (let i in event.getGuestList()) {
          let guest = event.getGuestList()[i];

          body.insertParagraph(ref + 2, guest.getEmail());
        }
        body.replaceText('##Attendees##', 'Attendees');
      }

      // Replaces the tag with a link to the agenda document
      const agendaUrl = 'https://docs.google.com/document/d/' + newDoc.getId();
      description = description.replace(
          '#agenda',
          '<a href=' + agendaUrl + '>Agenda Doc</a>',
      );
      event.setDescription(description);

      // Invites attendees to the Google doc so they automatically receive access to the agenda
      newDoc.addEditor(newDoc.getOwner());

      for (let i in event.getGuestList()) {
        let guest = event.getGuestList()[i];

        newDoc.addEditor(guest.getEmail());
      }
    }
  }
  return;
}

/**
 * Creates an event-driven trigger that fires whenever there's a change to the calendar.
 */
function setUp() {
  let email = Session.getActiveUser().getEmail();
  ScriptApp.newTrigger("onCalendarChange").forUserCalendar(email).onEventUpdated().create();
}

修改

您可以根据需要尽情修改示例。下面列出了您可以进行的一些可选更改。

更新与会者对议程文档的权限

该脚本会向参加者授予修改权限。如果您想将权限限制为仅查看,请在代码的以下部分将 addEditor 方法替换为 addViewer 方法:

     for (let i in event.getGuestList()) {
       let guest = event.getGuestList()[i];

       newDoc.addEditor(guest.getEmail());

修改议程文档模板

如需更新议程文档模板,请按以下步骤操作:

  1. 在日历活动中创建第一个议程后,打开 Google 云端硬盘。
  2. 打开名为 Agenda Maker - App 的文件夹。
  3. 打开 Agenda TEMPLATE## 文档,然后进行修改。

贡献者

此示例由产品管理和平台策略顾问 Jeremy Glassenberg 创建。您可以在 Twitter 上找到 Jeremy:@jglassenberg

此示例由 Google 维护,并由 Google 开发者专家提供帮助。

后续步骤