GASでアジェンダ作成と関係者への通知を自動化した話
2017/05/12 12:35
  • 自動化に至った経緯

    いきなりですが、ミーティングのアジェンダ作成って結構面倒な作業だったりしますよね。

    雛形から作成しようにも、「前回結論をペンディングした議題を反映しておきたい」など、前回の議事録からコピーしてくるケースも多いかもしれません。
    ぼくの職場では少なくともそうでした。

    毎週何も考えずに前回の議事録をコピーして、日付等必要な部分だけ書き換えたあとに
    メンバーに共有するという作業を行っていました。
    さすがに面倒になったので、Google Apps Scriptを使って自動化することにしました。


    概要

    以下にどんなツールを作ったのか、概要を記載します。

    0.Google Apps Script(以下GAS)を使う
    1.議事録のコピー
      →前回の議事録をコピーし、次回用にファイル名や日付をリネームする
       また、3ヶ月おきに自動的にフォルダを作成する
    2.短縮URLの取得
      →生成された次回用のアジェンダの短縮URLを取得
       (slackで共有する際、ほんの少しスマートになる程度)
    3.slackにて通知
      →slackの任意の部屋へ次回アジェンダのURLをポストする
       (slackのincoming-webhookをつかう)
    4.GAS側でトリガーを設定
      →毎週、MTGの前日にスクリプトが動くように設定
    5.正常に動くことを祈る
      →ただ祈る…
    

    コード

    雑にコードを紹介します。
    // ドキュメントをコピーする
    /** 
     * copyDocument() をGASのトリガーにセットする
     * 例:MTG前日の午前9時に実行されるようにする, etc.
     **/
    function copyDocument() {
      var date = new Date();
      date.setDate(date.getDate() + 1); // MTG日をセットする(MTG前日通知を想定しているため、 +1 日)
      
      var previous_file = getPreviousFile(date); // 前回のMTG議事録を取得する
      var file_name = previous_file.getName();
      
      var folder = toFolder(date);  // 保存先フォルダを指定
    
      // 以下で生成するアジェンダのファイル名を生成するための準備
      var append_date = Utilities.formatDate(date, 'JST', 'yyyyMMdd');
      var replace_date = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
      var lastweek = date.setDate(date.getDate() - 7);
      var file_prefix = Utilities.formatDate(date, 'JST', 'yyyyMMdd');
      
      // 前回の議事録をコピーし、次回用にリネームする
      // ファイル名が「20170512_ほげMTGアジェンダ」となる。形式を変えたい場合は要調整
      var new_file = previous_file.makeCopy(file_name.replace(file_prefix, append_date), folder);
      
      folder.addFile(new_file); // 指定したフォルダにいま生成したアジェンダを追加
        
      // Update date in the document
      // ファイル内に前回のMTG開催日が記入されている場合は、次回の日付に置換する
      updateContent(new_file, replace_date);
      
      // slack 通知用のメッセージを生成
      var body_slack = '@here\n *アジェンダ生成通知* ' + '\n> _アジェンダが生成されました。_\n' + '> ' + shortenUrl(new_file.getUrl()) + '\n\n';
      body_slack += '各自アップデートをお願いします。'
      
      // set channel name
      // 通知先チャンネルをセット
      var channel = 'xxxx';
      postToSlack(channel, body_slack);  // slackで通知
    }
    
    // 先週の議事録を取得する
    function getPreviousFile(date) {
      var lastweek = date.setDate(date.getDate() - 7);  //前回のMTG日。 date には次回開催日が入っているので、単純に一週間(7日)分引く。
      var file_prefix = Utilities.formatDate(date, 'JST', 'yyyyMMdd');
      var folder_name = getFolderNameForCore(date);  // 日付にマッチしたフォルダ名を取得
      var base_folder = DriveApp.getFolderById('xxxxxxxxxxxxxx');  // 保存先の親フォルダを取得
      var folder = base_folder.getFoldersByName(folder_name).next();
      
      var condition = "title contains '" + file_prefix + "'";
      date.setDate(date.getDate() + 7);  // 日付を一週間後に戻す
      
      return folder.searchFiles(condition).next(); // 指定フォルダ内にある検索条件にマッチするファイルを返す
    }
    
    // 日付を最新のものに置換
    function updateContent(file, date) {
      var document = DocumentApp.openById(file.getId());
      var content = document.getBody();
      // 日付を正規表現で置換
      // 対象期間: 2010/01/01 - 2026/12/31
      return content.replaceText("20[1-2][0-6]/[0-1][0-9]/[0-3][0-9]", date);
    }
    
    // コピー先のフォルダオブジェクトを取得
    function toFolder(date) {
      var folder_name = getFolderNameForCore(date);
      var base_folder = DriveApp.getFolderById('xxxxxxxxxxxxx');
      var folders = base_folder.getFoldersByName(folder_name);
      
      return folders.hasNext() ? folders.next() : base_folder.createFolder(folder_name);
    }
    
    // フォルダ名を取得
    /**
     * 3ヶ月毎にフォルダを分ける
     * 「2017_04_to_06」のようなフォルダ名
     **/
    function getFolderNameForCore(date) {
      var current_month = Utilities.formatDate(date, 'JST', 'M');
      var year = Utilities.formatDate(date, 'JST', 'yyyy');
      var month = {};
      
      var val = current_month % 3;
      
      switch (val) {
        case 0:
          month.start = current_month - 2;
          break;
        case 1:
          month.start = current_month;
          break;
        case 2:
          month.start = current_month - 1;
          break;
        default:
          month.start = 1;
          break;
      }
      
      month.end = parseInt(month.start) + 2;
      
      var folder_name = year + '_' + zeroPadding(month.start) + '_to_' + zeroPadding(month.end);
      return folder_name;
    }
    
    // ゼロパディング
    function zeroPadding(num) {
      return ('0' + num).slice(-2);
    }
    
    // slackで通知する際に短縮URLを使う
    function shortenUrl(long_url) {
      var url = UrlShortener.Url.insert({
        longUrl: long_url
      });
      return url.id;
    }
    
    // slackに投稿する
    function postToSlack(channel, content, sender, icon) {
      if (!sender) var sender = 'wkm-bot';
      if (!icon) var icon = ':information_source:';
      
      var hook_url = ''; // slack のincoming webhook
      
      var params = {};
      var slack_content = {
        "channel": channel,
        "username": sender,
        "text": content,
        "icon_emoji": icon,
        "link_names": 1  // @mention を有効にする。デフォルトでは無効
      };
      var payload = JSON.stringify(slack_content);
      
      params = {"method": "POST", "payload": payload};
      return UrlFetchApp.fetch(hook_url, params);
    }
    

    うまく実行できると、以下のような感じで自動でフォルダ/ファイルが生成されていきます。

    フォルダ一覧

    所感

    自分で言うのもあれですが、地味に便利でした。

    大きなことをGASでやろうとするとしんどいイメージですが、簡単な通知や
    docs, spreadsheetと連携した便利系ツールを作るのにGASはおすすめです。

    実際に前案件ではアジェンダ自動生成に加えて、WBSから各機能のタスク進捗を
    毎日2回(朝・夕)slackに自動でポストしたりもしていました。

    それでは素敵なGASライフを!


    人気ブログランキングへ ブログランキング・にほんブログ村へ
    ↑応援よろしくお願いします!m(_ _)m

  • <2017/05/12 12:17>
  • ツール
  • Google Apps ScriptSlack自動通知自動生成ミーティングMTGアジェンダ議事録GAS
  • 新しい記事へ
    [Django] Google Search Console などでサイトのドキュメントルートにHTMLファイルを設置する方法

    古い記事へ
    [Android] フォトフレームアプリを公開しました

profile picture

自己紹介的な何か

@wkmettyでついったーやってます。時々。 6年間勤めたゲーム会社を2018年2月に退職しフリーランスのプログラマに。 WordPress Core, WP-CLI コントリビューター。 お仕事募集中です。