[非エンジニアでもできる] SlackのステータスをGoogleカレンダーから取得する


## はじめに
本記事は下記を参考にしたものです。

* [GoogleカレンダーとSlackステータスを連携する【GAS】](https://qiita.com/zama_8722/items/7d06767b19dec745c3bf)
* [GASを使ってGoogle CalendarとSlack Statusを同期する](https://qiita.com/tamanugi/items/1c083dac5d35f65092d9)

## 背景
ちょっとした心がけですが、お昼休憩に入るときはSlackのステータスを「🍙 おひる」に手動で変更しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/570f216c-fe0f-b48f-b9ff-a50fe30be5d1.png" width=50%>

こうしている理由は、仕事の連絡やメンションをくれた方が「あれ？返事遅いな」と思った際に、ステータスを見て「休憩か」とわかってもらうためです。

でも、結構な頻度で変更し忘れる & 手動でやるのが単純にめんどくさいので、「誰か同じことをうまくやっていないかな」と思い記事を書くに至っています。


#### うらばなし
[Google Calendar と Slack を連携させる](https://slack.com/intl/ja-jp/help/articles/206329808-Google-Calendar-%E3%81%A8-Slack-%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B#slack-%E3%81%A7-google-%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)

実は、Googleカレンダーに予め予定を入れておき、予定の時間になると、Slackのステータスが切り替わる機能を使用することは、現状ではマウスでポチポチやれば簡単にできるはずです。著者も過去に使っていました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/35aece3e-3193-4d27-2cbe-df214c17b770.png" width=50%>

ステータスが「in a meeting」になるだけで、内容は不明。
お昼ごはん中なのか、会議中なのか、外出中なのかは、本人以外はわからない状況。
まぁこれはこれで良くて、どんな会議なのか知られる必要が特にない方にとってはgoodな機能。


しかし、やはり私としては「ご飯中」だ、「会議中」だ、「犬の散歩中」だ、「病院なう」だということを、slackのステータスにどうしても表示させたい!!という個人的な意向もあり、今回GASを使ってやってみるということになりました。

## やりたいことのイメージ

ざっと箇条書きにするとこんな感じです。
１→２を３の方法で繰り返し続けるというイメージでしょうか（ただし４をオプションでやりたい）。

1. Googleカレンダーの予定タイトルを取得する
1. 取得したタイトルをSlackのステータスとして表示する
1. 何分かの間隔で繰り返し取得し続け、ステータスを維持・更新・削除する
1. 予定がない場合は指定したtextや絵文字を表示できるようにする


## 準備する

必要なものは以下の３点だと思います。

* ① GoogleカレンダーID
* ② アクセスTOKEN
    * Slackアプリ作成する
    * Slack API取得する
* ③ GASコード
    * コード内に必要情報を埋め込む
    * トリガーの設定 

#### ① GoogleカレンダーID

* [Googleカレンダー](https://calendar.google.com/calendar)にアクセスする

|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/bbbb8dbb-6028-09d5-16ed-9dad41e5d279.png" width=100%>|
|--|

* Slackのステータスに使いたいカレンダーの「設定と共有」を開く
* 「カレンダーの統合」を開く
* カレンダーIDをコピーして保存しておく
    * グループのカレンダーだと「＠」以降のidがgroup.cakender.google.comになるようですが、気にせずコピーする。


#### ② アクセスTOKEN

まずは、Slack APIを使うためにSlackアプリを作成します。

* Slack [apiページ](https://api.slack.com/apps)から「create NewApp」を開く（From scratchでOK）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/057d8d6a-44c5-278e-40e1-1f74cdf5309a.png" width=80%>

* 詳細ページの「OAuth & Permissions」を開き「Scope」へ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/5fe81b3b-40e7-1518-8b12-e998a7e94022.png" width=80%>

*  「AppName」を適宜記入し、適用する「Workspace」を選択 > 「Create App」へ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/8ffbff98-dd01-cb37-b695-d4720e8a5e65.png" width=80%>

* 「Add an OAuth Scope」をクリックし、”users.profile:write”を追加する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/735598ab-ebd5-c949-ecc3-acbe90e9f9b0.png" width=80%>

* 「OAuth Tokens & Redirect URLs」で「Install to Workspace」をクリック
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/33f2a03b-de53-5650-92b1-b83f1fecc2e4.png" width=50%> 

* 権限リクエストを許可する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/94a93ebd-6e58-d2c1-f420-876c31813f80.png" width=50%>

* 取得した「User OAuth Token」をコピーして保存しておく
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/f1d03382-d1b1-1938-bae0-c38496a9e4cd.png"  width=50%>


#### ③ GASコード

GASコードはgoogleドライブから簡単に作成できます。

* [G Suite Developer Hub](https://www.google.com/script/start/)にアクセスし、「Start Scripting」をクリック 
    * もしくは、GoogleDrive画面から「マイドライブ」 > 「その他」 > 「Google App Script」 で作成
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/b21b04ad-5b2e-7953-ce38-7acfd9218144.png" width=50%>

* Googleアカウントでログインし、「新しいプロジェクト」で新規作成する。

|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/7ee5579d-79a8-0746-cffd-b406ddb517f3.png">|
|--|

* 下記に添付しているコードを貼付け、保存する。
    * 補足：21/10/15 コードをきれいにインデントしました
    * 引用元：[この記事](https://qiita.com/zama_8722/items/7d06767b19dec745c3bf#gas%E3%82%B3%E3%83%BC%E3%83%89
) の 「GASコード」部分

ちなみに、[SlackAPIの公式ドキュメント](https://api.slack.com/methods/users.profile.set#http_request__updating-a-users-current-status)にいろんな情報がのっているので載せておきます。
特に"var event_status"のbody部分には様々なステータスがあるようです。

```java:SlackStatus_Writer.gs
/*******************************************
その1：Googleカレンダー予定 → Slackステータス用に整形し返す
*******************************************/
function createStatusText(event) {
 // 整形した開始時刻・終了時刻
 var start = event.getStartTime().getHours() + ":" + ("00" + event.getStartTime().getMinutes()).slice(-2);
 var end = event.getEndTime().getHours() + ":" + ("00" + event.getEndTime().getMinutes()).slice(-2);
 // ステータステキスト
 var text = event.getTitle() + " (" + start + "〜" + end + ")";
 
 // イベントがある時のステータス
 var event_status = {
     "profile": JSON.stringify({
     "status_text": text,
     "status_emoji": ":すきなアイコンのコマンドを入力する:",
      "status_expiration": 0 
     })
  };
 return event_status;
}
 
/*******************************************
その2：作成済ステータス → SlackWebAPI経由 → プロフィールへ
*******************************************/
function postSlackStatus(status) {
 // アクセス情報
 const TOKEN = "準備の②で取得したUser OAuth Tokenをここに";
 const URL = "https://slack.com/api/users.profile.set";
 
 // HTTPヘッダー
 const headers = {
   "Authorization" : "Bearer " + TOKEN
 };
 
 //POSTデータ
 var option = {
   "Content-Type": "application/json",
   "headers": headers,
   "method": "POST",
   "payload": status
 };
 
 var fetch = UrlFetchApp.fetch(URL, option);
 
}
 
/*******************************************
その3：カレンダーの予定取得 → Slackステータスを更新
*******************************************/
function main() {
 // カレンダーID
 const ID = "準備の①で取得したカレンダーIDはここ";
 // 今日の日付
 var date = new Date();
 // カレンダーから今日の予定を取得
 var calendar = CalendarApp.getCalendarById(ID);
 var events = calendar.getEventsForDay(date);
  // 今日のイベントがない場合は何もしない
 if (events.length !== 0){
   // イベントがないとき
   var set_status = {
     "profile": JSON.stringify({
     "status_text": "MTG/Talk/Huddle OK Time!",
     "status_emoji": ":shigotoneko:",
     "status_expiration": 0 
     })
   };
   for (var i in events){
     // 終日イベントの場合はスルー
     if (events[i].isAllDayEvent()) {
       continue;
     }
     // 今が予定の開始時刻以降で終了時刻以前なら今はその予定の最中 -> ステータス変更
     if (events[i].getStartTime() <= date && events[i].getEndTime() >= date) {
       set_status = createStatusText(events[i]);
       break;
     }
   }  
 postSlackStatus(set_status);
 }
}
```

## コード手直し

その１〜その３のブロック（関数）で成り立っているコードになっています。
各自準備してもらった内容を記入したり、表示内容を決めてもらう必要がある箇所は、以下の通りです。

```diff:その１箇所：予定がある際、Slackのステータのスアイコンを決める
- "status_emoji": ":すきなアイコンのコマンドを入力する:"
+ "status_emoji": ":calender:" 
```

```java:その2箇所：準備②で保存したTOKENを貼付する
const ID = "準備の①で取得したカレンダーIDはここ";
```


```java:その2箇所：Googleカレンダーに予定がない際のアイコンとメッセージを決める
"status_text": "Free time!",
"status_emoji": ":dancer:"
```

```java:その３箇所：GoogleカレンダーIDを貼付する
const ID = "準備の①で取得したカレンダーIDはここ";
```

## コードを動かす

さて、コードを動かす準備は整いました。
コード実行時には、どのメソッドを最初に動かすかを指定できます。
必ず、今回はmainを選択してから実行するようにしましょう。
また、初回のみGoogleカレンダーへのアクセスの確認が必要なため、その設定も済ませてください。

|![スクリーンショット 2021-10-13 16.00.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/7c86a3d3-5db8-a5d7-98e0-a8d3b9bfba23.png)|
|--|

うまく動けば、画面下部分の「実行ログ」に開始と終了のお知らせが表示されるはずです。
![スクリーンショット 2021-10-13 16.03.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/d52adbc6-9cac-7dbd-6198-fb194fe34730.png)

## 定期実行の設定をする

ここまでこれば、あとは定期実行させる設定をするのみです。

* 画面の左部分から時計のマーク「トリガー」をクリックする
* 「トリガーを追加」をクリックする

|![スクリーンショット 2021-10-13 16.10.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/95b83326-5ef4-b4d6-df56-c13c8f1c9332.png)|
|--|

* 赤枠部分の設定を行い保存をクリックする
    * 赤枠以外の部分はお好みでどうそ

|![スクリーンショット 2021-10-13 16.14.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/da277c48-c88e-e807-d7aa-61f0cc9ffdb9.png)|
|--|

こうすることで、指定した時間間隔でGASが定期実行されます。

## 完成すると

カレンダーで作成した予定がSlackのステータスに反映されていることが確認できるはず。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/5c9506e8-ff4a-f77f-7dae-321e18bfbab6.png" width=50%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/6528721f-0f7e-eaed-9a54-3855b3ca94e5.png" width=50%>


## 補足：アイコン七変化（21/10/15追記）

予定内容に特定の文字が含まれている場合、内容と関連するアイコン表示をさせたい！とおもい、アイコンのバライティを増やすことにしました。

数パターンの分岐を追加してみました。「その１」のコード部分だけ掲載しておきます(それ以外の部分その２と３はそのままでOK)。

コード全文は[こちら](https://github.com/aAyakaYamamoto/SlackStatus_byGooglecal)

```java:予定タイトルごとのアイコン七変化
/*******************************************
 その1：Googleカレンダー予定 → Slackステータス用に整形し返す
*******************************************/
function createStatusText(event) {
  // 整形した開始時刻・終了時刻
  var start = event.getStartTime().getHours() + ":" + ("00" + event.getStartTime().getMinutes()).slice(-2);
  var end = event.getEndTime().getHours() + ":" + ("00" + event.getEndTime().getMinutes()).slice(-2);
  // ステータステキスト
  var text = event.getTitle() + " (" + start + "〜" + end + ")";

  // イベントがある時のステータス
  if(/(院|健康診断)/.test(event.getTitle())){
    var event_status = {
      // 病院系統のとき
      "profile": JSON.stringify({
      "status_text": "病院なう" + " (" + start + "〜" + end + ")",
      "status_emoji": ":hospital:"
      })
    };
  }else if(/(予定あり|外出|訪問|社外)/.test(event.getTitle())){
    var event_status = {
      // 社外のとき
      "profile": JSON.stringify({
      "status_emoji": ":gaisyutu_now:"
      })
    };
  }else if(/(打ち合わせ|社内|MTG|mtg|定例|会議|確定|up|UP|様|on|meet)/.test(event.getTitle())){
    var event_status = {
      // MTG・定例・1on1・打ち合わせ系統のとき
      "profile": JSON.stringify({
      "status_text": "MTG中" + " (" + start + "〜" + end + ")",
      "status_emoji": ":google_meet_new:"
      })
    };
  }else if(/(休|午後|午前)/.test(event.getTitle())){
    var event_status = {
      // 休暇系統のとき
      "profile": JSON.stringify({
      "status_text": text,
      "status_emoji": ":palm_tree:"
      })
    };
  }else if(/(LUNCH|lunch|Lunch|お昼|おひる|ご飯|ごはん)/.test(event.getTitle())){
    var event_status = {
      // ご飯
      "profile": JSON.stringify({
      "status_text": "ごはん食べてます" + " (" + start + "〜" + end + ")",
      "status_emoji": ":rice_ball:"
      })
    };
  }else if(/(作業|自作業|TODO|todo|Todo|案件作業)/.test(event.getTitle())){
    var event_status = {
      // 作業系統
      "profile": JSON.stringify({
      "status_text": "作業中" + " (" + start + "〜" + end + ")",
      "status_emoji": ":heads-down:"
      })
    };
  }else if(/(dog|cat|散歩|walk)/.test(event.getTitle())){
    var event_status = {
      // 散歩系統
      "profile": JSON.stringify({
      "status_text": "散歩中" + " (" + start + "〜" + end + ")",
      "status_emoji": ":walking-the-dog:"
      })
    };
  }else if(/(退社|退勤|定時)/.test(event.getTitle())){
    var event_status = {
      // 退勤系統
      "profile": JSON.stringify({
      "status_emoji": ":taikinn:"
      })
    };
  }else if(/(出社|移動)/.test(event.getTitle())){
    var event_status = {
      // 移動系統
      "profile": JSON.stringify({
      "status_text": "移動中" + " (" + start + "〜" + end + ")",
      "status_emoji": ":train:"
      })
    };
  }else{
    var event_status = {
      // それ以外の予定
      "profile": JSON.stringify({
      "status_text": text,
      "status_emoji": ":spiral_calendar_pad:"
      })
    };
  }
  return event_status;
}
```

Slackではこんな感じで表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/4e605492-9400-8875-87f7-ee0c17874a45.png"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/3a0d858b-2b88-f17e-a2aa-9d11cf93bc60.png"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/f9b80b92-102d-0010-08f8-6bab12f7e02c.png"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/eeb1bc77-966b-4b70-5ea9-3625d7cf0886.png"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/77652091-c24f-d105-01e4-851cc871f3ab.png"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614512/a48c65e8-5bf0-3034-1e3b-5cc9d7e56e06.png">