G Suiteメンバーのスケジュールをチャットワークから取得してみる

こんにちは！新卒一年目エンジニアの和尚です:sparkles:
今日は入社して２週間後くらいに作った仕事を効率化するシステムについて皆さんに共有したいと思います！

# 作成するに至った経緯
入社してすぐに僕に大きく立ちはだかったもの...それは電話応対です:disappointed_relieved:

大きな研修も無くすぐに業務に入ったため、コーディングをしながら鬼のように鳴る電話の応対をしなければなりませんでした。新人なので取らないわけにはいかないと思い電話をとっていたのですが、如何せん業務に集中できない...:confused:

**指定された社員のスケジュールを素早く簡単に確認したい...**

そこで、↑を簡略化することで少しでも電話応対の時間を減らせないかと考えついたものがこちらのシステムです。

## 使用するもの
弊社では最近スケジュール管理をrakumoに移行しました。rakumoはGoogleカレンダーと連動しているため、GoogleカレンダーAPIから情報を取得することができます。入力と出力には業務連絡に使用しているチャットワークを使用します。基本的にチャットワークは作業中も常に開いているので、これが最適でしょう。

GoogleカレンダーAPIを使用するのに、検索する人のメールアドレスが必要です。通常G Suiteの管理者でないとメンバーのメールアドレスは確認できないのですが、Admin Directory APIを使用することで検索することができるのでこちらも使用します。

![グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243225/42150600-e0b1-fcdc-dfdf-e836ad698069.png)


わざわざこのレベルのシステムのためにサーバーを立てるのもアホらしいのでGoogle Apps Scriptを使用します。
GASにはGoogleで提供されているAPIを簡単にできる仕組みが備わっているため、今回のシステム作成には最適です。

-  (Google) Calendar API
-  (Google) Admin Directory API
-  チャットワークAPI

# 開発手順
GASの細かい説明は省きます。基本的にはJavaScriptやTypeScriptで書くのですが、ES6に対応していないためES6を使って書きたい方はコンパイルが必要なので検索してみてください。

## 1.チャットワークAPIをGASから叩けるようにする
GASにはdoPost関数が用意されており、公開したプロジェクトに対してPOSTでリクエストが送られると、この関数が実行されるようになっています。この記事に辿りつく方なら一度はチャットワークからGASを使って何かしらのシステムを作ったことがあると思うので、詳しくは書かないことにします。

初めての方は、設定の仕方はググれば記事がいっぱい見つかると思うのでググりましょう:relieved:

## 2.検索条件の指定
webhookを設定すると、指定したルームで誰かがメッセージを送る度にGASの方にPOSTでメッセージが送られます。
今回の目的は社員のスケジュールを確認することなので、検索に必要なパラメータは**日付**と**氏名**です。

日付は**現在**のスケジュールもしくは**今日**、**明日**のスケジュールがあれば十分だったので、以下のようなメッセージを送ると3つの条件の中から指定した日時のスケジュール検索ができるような仕組みにしました。
<img width="169" alt="スクリーンショット 2019-06-30 2.24.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243225/176d6afd-5de7-0a53-9558-3ba73e1c3344.png">

```javascript:main.gs
/**
* チャットワークからのPOSTリクエスト受け取り関数
*/
function doPost(e) {
  var postData = JSON.parse(e.postData.contents);
  var message = containsKeyword(postData);
}

/**
* キーワードが含まれているかのチェック
*/
function containsKeyword(postData){
  var body = postData.webhook_event.body;
  var name = body.substr(4);
  var schedule;

  if(body.match(/いま：：/) || body.match(/いま::/)){
    sendMessage("只今検索中...(devil)");
    schedule_search(name,"now");
  }else if(body.match(/今日：：/) || body.match(/今日::/)){
    sendMessage("只今検索中...(inlove)");
    schedule_search(name,"today");
  }else if(body.match(/明日：：/) || body.match(/明日::/)){
    sendMessage("只今検索中...8-|");
    schedule_search(name,"tomorrow");
  }
}

//schedule_search関数....名前と日時条件を引数にスケジュールを検索する関数
```
メッセージの先頭に日時を表す単語、その後にコロンを二つ並べて名前を入れると検索されるような関数です。
急いでいる場合が多いので、タイプが少なくて済むようなフォーマットになっています。

## 3.メールアドレスを取得する
それでは検索条件が揃ったので、指定された人のメールアドレスを取得してみましょう。ちなみに検索は複数人検索できます。

```javascript:main.gs
function schedule_search(name, date){
  var usersInfo = getUsersInfo(name);

  //~以下は4で詳しく
}

/**
* - ユーザー情報取得 -
* メンバー全員の中から検索条件が部分一致する人のフルネームとメールアドレスを返す
*/
function getUsersInfo(name){
  var pageToken, page;
  var users_info = [];
  do {
    page = AdminDirectory.Users.list({ //※1
      domain: 'G.Suite.domain', //G Suiteのドメイン名を入力します
      viewType: 'domain_public', //ここをdomain_publicにすることで管理権限がなくてもメンバーの情報を取得できます。
      pageToken: pageToken
    });
    var users = page.users;
    if(users){
      users.forEach(function(user){
        var info = {
          name: "",
          email: ""
        }
        if(user.name.fullName.match(name)){
          info.name = user.name.fullName;
          info.email = user.primaryEmail;
          users_info.push(info);
        }
      });
    }
    pageToken = page.nextPageToken;
  }while(pageToken);
  return users_info;
}
```
**※1.** AdminDirectory.~の部分がAdminDirectoryAPIを使っている部分です。
GASの[リソース]メニューから[Googleの拡張サービス]を選択してAdmin Directory APIをONにしましょう。(ついでにCalendar APIも)
設定画面の下にも注意書きがありますが、Google Cloud Platform API ダッシュボードでAPIを有効にしておかないといけません。

Googleが提供するAPIがこの設定画面をONにするだけで使用できるので、とても楽ですね:heart_eyes:
書き方も公式マニュアルがあるので難しくありません。


## 4.取得したメールアドレスからスケジュールを取得する
ユーザー情報が取得できたら、そのメールアドレスを使用してスケジュールを取得してみましょう。
うーん時間の箇所の条件文が長すぎるので時間の処理をもう少し美しくリファクタリングしたい...

- 今(now) ... 現在進行中の予定
- 今日(today) ... 今日の予定
- 明日(tomorrow) ... 明日ないしは月曜日の予定(金曜日に明日の検索をした場合のみ)

```javascript:main.gs
/**
* スケジュールを返す
*/
function schedule_search(name, date){
  var usersInfo = getUsersInfo(name);
   
  usersInfo.forEach(function (info){
    var message = "";
    var calendarId = info.email;
    var optionalArgs = {
      timeMin: (new Date(new Date().setHours(0, 0, 0, 0))).toISOString(),
      showDeleted: false,
      singleEvents: true,
      maxResults: 15,
      orderBy: 'startTime'
    };
    var response = Calendar.Events.list(calendarId, optionalArgs); //Calendar APIの呼び出し
    var events = response.items;
    var searchMessage = "【" + info.name + "さんの"+ getDateStr(date) +"のスケジュール】︎\n";
    message = searchMessage;
      if(events.length > 0){
        for(var i=0;i<events.length;i++){
          var eDate = getDate(events[i].start.dateTime, events[i].end.dateTime);
          var location = events[i].location;
          var summary = events[i].summary;
          if(date === "now"){
            if(eDate.start.Time < eDate.now.Time && eDate.now.Time < eDate.end.Time){
              message += "・" + twoDigi(eDate.start.Hour) + ":" + twoDigi(eDate.start.Minute) + " - " + twoDigi(eDate.end.Hour) + ":" + twoDigi(eDate.end.Minute) + " " + summary + "\n";
            }
            break;
          }else if(date === "today"){
            if(eDate.start.Year === eDate.now.Year && eDate.start.Month === eDate.now.Month && eDate.start.Date === eDate.now.Date && eDate.end.Year === eDate.now.Year && eDate.end.Month === eDate.now.Month && eDate.end.Date === eDate.now.Date){
              message += "・" + twoDigi(eDate.start.Hour) + ":" + twoDigi(eDate.start.Minute) + " - " + twoDigi(eDate.end.Hour) + ":" + twoDigi(eDate.end.Minute) + " " + summary + "\n"; 
            }
          }else if(date === "tomorrow"){
            if(eDate.now.Day === 5){ //金曜日の場合は3日後の月曜日の予定を取得
              if(eDate.start.Year === eDate.now.Year && eDate.start.Month === eDate.now.Month && eDate.start.Date === eDate.now.tommorow + 2 && eDate.end.Year === eDate.now.Year && eDate.end.Month === eDate.now.Month && eDate.end.Date === eDate.now.tommorow + 2){
                message += "・" + twoDigi(eDate.start.Hour) + ":" + twoDigi(eDate.start.Minute) + " - " + twoDigi(eDate.end.Hour) + ":" + twoDigi(eDate.end.Minute) + " " + summary + "\n"; 
              }
            }else if(eDate.now.Day === 6){ //土曜日の場合2日後の月曜日の予定を取得
              if(eDate.start.Year === eDate.now.Year && eDate.start.Month === eDate.now.Month && eDate.start.Date === eDate.now.tommorow + 1 && eDate.end.Year === eDate.now.Year && eDate.end.Month === eDate.now.Month && eDate.end.Date === eDate.now.tommorow + 1){
                message += "・" + twoDigi(eDate.start.Hour) + ":" + twoDigi(eDate.start.Minute) + " - " + twoDigi(eDate.end.Hour) + ":" + twoDigi(eDate.end.Minute) + " " + summary + "\n"; 
              }
            }else{
              if(eDate.start.Year === eDate.now.Year && eDate.start.Month === eDate.now.Month && eDate.start.Date === eDate.now.tommorow && eDate.end.Year === eDate.now.Year && eDate.end.Month === eDate.now.Month && eDate.end.Date === eDate.now.tommorow){
                message += "・" + twoDigi(eDate.start.Hour) + ":" + twoDigi(eDate.start.Minute) + " - " + twoDigi(eDate.end.Hour) + ":" + twoDigi(eDate.end.Minute) + " " + summary + "\n"; 
              }
            }
          }
        }
      }
    if(message === searchMessage){
      message = message + "予定が見つかりませんでした...";
      sendMessage(message);
    }else{
      sendMessage(message);
    }
  });
}

//sendMessage関数 ... チャットワークに送る関数

//時間取得
function getDate(startDate, endDate){
  var nowTime = new Date;
  var nowYear = nowTime.getFullYear();
  var nowMonth = nowTime.getMonth();
  var nowDate = nowTime.getDate();
  var nowDay = nowTime.getDay();
  var tommorow = nowTime.getDate() + 1;
  var returnNowDate = {
    Time: nowTime,
    Year: nowYear,
    Month: nowMonth,
    Date: nowDate,
    Day: nowDay,
    tommorow: tommorow
  };
  
  var startTime = new Date(startDate);
  var startYear = startTime.getFullYear();
  var startMonth = startTime.getMonth();
  var startHour = startTime.getHours();
  var startMinute = startTime.getMinutes();
  var startDate = startTime.getDate();
  var returnStartDate = {
    Time: startTime,
    Year: startYear,
    Month: startMonth,
    Hour: startHour,
    Minute: startMinute,
    Date: startDate,
  };

  var endTime = new Date(endDate);
  var endYear = endTime.getFullYear();
  var endMonth = endTime.getMonth();
  var endHour = endTime.getHours();
  var endMinute = endTime.getMinutes();
  var endDate = endTime.getDate();
  var returnEndDate = {
    Time: endTime,
    Year: endYear,
    Month: endMonth,
    Hour: endHour,
    Minute: endMinute,
    Date: endDate
  }
  return {now: returnNowDate, start: returnStartDate, end: returnEndDate};
}

//時間を2桁に整形する
function twoDigi(date){
  return ("0" + date).slice(-2);
}

//時間帯を文字にしてを返す
function getDateStr(date){
  if(date === "now"){
    return "今現在";
  }else if(date === "today"){
    return "今日";
  }else if(date === "tomorrow"){
    var d = new Date;
    if(d.getDay() === 5 || d.getDay() === 6 || d.getDay() === 7){
      return "月曜日";
    }
    return "明日";
  }else{
    return;
  }
}
```

## 5.チャットワークに送る
最後は取得した情報をチャットワークに送りましょう。

```javascript:main.gs
/**
* チャットワークへとPOSTメッセージを渡す
*/
function sendMessage(message) {
  var CHATWORK_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; //チャットワークAPIキー
  var roomId = "YYYYYYYYYY"; // チャットワークルームID
  var params = {
    headers: {"X-ChatWorkToken": CHATWORK_KEY},
    method: "post",
    payload: {body: message}
  };
  var url = "https://api.chatwork.com/v2/rooms/" + roomId + "/messages";
  UrlFetchApp.fetch(url,params);
}
```
<img width="444" alt="スクリーンショット 2019-06-30 3.02.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243225/e972c020-0f43-e042-ec96-47c76667ad2b.png">

# 問題点
- **今日**と入力しても0時~24時までに設定してあるスケジュールしか取得できない(終日の予定が取得できない)

# 終わりに
ざっと駆け足でチャットワークからG Suite内メンバーのスケジュールを取得する方法を見てきましたがいかがでしたでしょうか？
コードばかりで説明はしょり過ぎているような気もしますが、何かわからないことがありましたらコメントお願いします。
(コードが汚いのは新人のご愛嬌ということで笑)

Googleの拡張サービスを見ると、もっと色んな業務が効率化できそう感じがしますね。
せっかくエンジニアになったので作業効率化できるところはシステムをどんどん組んでいきたいなと思いました。

ちなみにこのサービスを作ったことで、電話応対の際のスケジュール確認のスピードはかなり上がりました。
しかし問題点の終日の予定を見逃して、一回怒られたので封印中...(作られる際は対応してくださいね:sleepy:)

ではでは!!