制定会议议程

编码水平:初级
时长:15 分钟
项目类型:使用事件驱动型触发器实现的自动化

目标

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

关于此解决方案

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

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

运作方式

该脚本会为日程创建一个文档模板。当您更新日历时,脚本会检查您是否在活动说明中添加了“#agenda”。如果标签存在,脚本会复制这个模板,将其添加到日历活动中,并分享给活动参加者。

Apps 脚本服务

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

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

前提条件

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

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

设置脚本

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

运行脚本

  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. 打开议程模板##文档,然后进行修改。

贡献者

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

此示例由 Google 在 Google 开发者专家的帮助下维护。

后续步骤