規劃會議議程

程式設計層級:新手
時間長度:15 分鐘
專案類型:透過事件導向觸發條件進行自動化作業

目標

  • 瞭解解決方案的功能。
  • 瞭解 Apps Script 服務在解決方案中的功能。
  • 設定指令碼。
  • 執行指令碼。

認識這項解決方案

自動在 Google 文件中建立議程文件,並附加至 Google 日曆會議。

新增至日曆活動的待辦事項螢幕截圖

運作方式

這個指令碼會建立議程的文件範本。當您更新日曆時,指令碼會檢查您擁有的事件,看看說明中是否包含「#agenda」。如果有這個標記,這個指令碼會複製範本並新增至日曆活動,然後與活動參與者共用。

Apps Script 服務

這項解決方案使用下列服務:

  • 雲端硬碟服務 - 檢查範本文件是否存在,若沒有,請為範本文件建立新的資料夾。為每個新議程建立範本文件的副本。
  • 文件服務:建立議程範本。
  • 日曆服務:使用「#agenda」標記檢查活動,並將活動說明更新為議程文件的連結。
  • 基礎服務:使用 Session 類別取得使用者的電子郵件。這有助於建構目前使用者的觸發條件。
  • 指令碼服務 - 建立一個觸發條件,每當使用者的日曆有變更時就會觸發。

必要條件

如要使用這個範例,您必須具備以下必要條件:

  • Google 帳戶 (Google Workspace 帳戶可能需要取得管理員核准)。
  • 可連上網際網路的網路瀏覽器。

設定指令碼

  1. 按一下下方按鈕,開啟「Make a 最多可能需要 for Meetings」(建立會議議程) Apps Script 專案的範例。
    開啟專案
  2. 按一下「總覽」圖示
  3. 在總覽頁面上,按一下「建立副本」圖示 用來建立副本的圖示
  4. 在複製的專案的函式下拉式選單中,選取「setUp」setUp
  5. 按一下「執行」
  6. 出現提示訊息時,請授權執行指令碼。如果 OAuth 同意畫面顯示警告「This app has not verification」(這個應用程式尚未驗證),請依序選取「Advanced」(進階) >「Go to {Project Name} (unsafe)」 (前往 {Project Name} (不安全))

執行指令碼

  1. 開啟 Google 日曆
  2. 建立新活動或編輯現有活動。
  3. 在說明中加入 #agenda 並儲存活動。
  4. 請查看電子郵件,取得與您共用文件的電子郵件通知。或者,您也可以重新整理日曆,然後再次點選活動,即可查看議程文件的連結。

所有參與者都會收到查看議程的電子郵件通知。這個指令碼會授予參與者編輯權限,但您可以編輯指令碼,以便更新參與者的議程文件權限

查看程式碼

如要查看這項解決方案的 Apps Script 程式碼,請點選下方的「查看原始碼」

查看原始碼

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. 開啟名為「待辦事項 - 應用程式」的資料夾。
  3. 開啟待辦事項 TEMPLATE## 文件並進行編輯。

貢獻者

這個範例是由 Jeremy Glassenberg、產品管理和平台策略顧問製作。在 Twitter 上尋找 Jeremy 網址:@j Glassenberg

本範例是由 Google 在 Google Developers 專家的協助下維護。

後續步驟