SlackBotに各人毎のGoogle MeetのURLを出してもらう

# 突然ですがこんな経験はないですか？

**Slack上でのやりとりでは限界があってビデオ通話で会話がしたいときに**

1. Slackで相手にMeetしましょうと声を掛ける
2. ブラウザで新しいタブ開く
3. MeetのURLを作成する
4. Slackに戻ってMeetのURLを共有してルームに入る

という流れ

画面移動とか地味にめんどくさくないですか？
僕はめんどくさいです
なのでSlackBotに自分専用のMeetのURLを出してもらうことにしました

# 実際に動いてるもの
塗りつぶし多くてちょっと見辛いですがslackから移動する必要がなくMeetのURLを共有できます

![スクリーンショット 2020-12-09 2.01.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/71947f19-c47e-d048-fe65-671416080f92.png)

# どうやっているのか
1. `Slack`
2. `GAS`
3. `Sheets API` / `Calendar API`
4. `Slack`

という流れでGAS経由でスプレッドシートで管理している個人ごとのURLを返します
スプレッドシートにURLがない場合はカレンダーAPIを使ってMeetのURLを生成してスプレッドシートに保存して返します
サーバレスで、GASは無料ホスティングできてとってもお手軽

# 設定していく
さっそくやってみたい！と思ってくれた人がいるかもしれないと信じてなるべく丁寧に手順書いていきます

## 1. Slackからの認証のためにGASを仮置きする
### 1-1. GoogleDriveで適当に右クリックしてGoogle Apps Scriptを作る
```js
function doPost(e) {
  return ContentService.createTextOutput(JSON.stringify(e));
}
```
![スクリーンショット 2020-12-12 17.52.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/994a8161-cd5d-d3ab-e176-a4eceeb3019d.png)

### 1-2. GASをアプリとして公開する
`ウェブアプリケーションとして導入` を選択
![スクリーンショット 2020-12-12 17.47.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/5662e099-64a2-b9e4-1e40-af4dac018bc0.png)
![スクリーンショット 2020-12-12 17.47.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/bbf4367f-2317-2343-e1bd-647825e7ee34.png)
![スクリーンショット 2020-12-12 17.47.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/39519be6-a374-fdb4-307a-766f49769c5d.png)

**ここで最後に発行されたURLをメモっておく**

## 2. SlackBotを作る
### 2-1. https://api.slack.com/apps にアクセスし`Create New App` からBotを作る
![スクリーンショット 2020-12-09 12.42.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/dbf3a726-c895-7205-db1e-37a82cea18cd.png)

![スクリーンショット 2020-12-09 12.44.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/ac9b6ba6-fd46-9d71-77e6-edded07f8dcd.png)

### 2-2. 左のメニューの `OAuth & Permissions` から Scopeを設定する
`Bot Token Scopes` で 

- `app_mentions:read`
- `chat:write`

を追加する

![スクリーンショット 2020-12-09 12.43.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/2d82724d-b378-03de-d090-dcab16a0a959.png)

### 2-3. 続けて、アクセストークンを発行する
![スクリーンショット 2020-12-12 18.08.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/c89d0b8d-6fb3-82d4-ecb8-f1f994505d46.png)
![スクリーンショット 2020-12-12 18.08.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/9165dfa8-0aa5-0784-a97f-48fd65305fdb.png)


- `Install To Workspace` を押す
- Botのアクセストークンが発行されるのでメモっておく


### 2-4. 左のメニューの `Event Subscriptions` から イベントの設定とGASエンドポイントの認証をする
![スクリーンショット 2020-12-12 17.49.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/e6a3e92b-2ba4-cdac-c388-95265a4ef26f.png)

- `Enable Events` をOnにする
- 1-2で発行したGASアプリのURLを `Request URL` へ入力する(成功すれば `Verified` になる)
- `Subscribe to bot events` で `app_mention` を追加する
- `Save Changes` で保存

## 3. スプレッドシートを作る
### 3-1. GAS同様に適当にドライブにスプレッドシートを作成する
### 3-2. URLからスプレッドシートのIDをメモる
`https://docs.google.com/spreadsheets/d/*************/edit#gid=0` の
*************の部分がIDとなる
GASで使うのでメモっておく

## 4. Google CalendarでAPI経由でMeetのURLを作るために適当にカレンダーを作る
### 4-1. `新しいカレンダーを作成` から適当にカレンダーを作る
https://calendar.google.com/calendar/u/0/r/settings/createcalendar にアクセスして作る

![スクリーンショット 2020-12-12 21.09.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/616f880b-e57f-3a62-93f8-d38a5bc733c3.png)



### 4-2. `カレンダーに登録` からさっき作ったカレンダーIDを確認する
https://calendar.google.com/calendar/u/0/r/settings/addcalendar にアクセスして確認

- さっき入力したカレンダー名でを入力して選択するとカレンダーの選択画面が出る

![スクリーンショット 2020-12-12 21.14.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/dc58d25e-1036-e0a4-7b9b-90e6a7ba50b4.png)

![スクリーンショット 2020-12-12 21.09.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/19703b7b-8f08-98f7-4ba6-c6c8f3c5c483.png)

- 下にスクロールするとカレンダーIDが確認できるのでメモしておく

![スクリーンショット 2020-12-12 21.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/0cfd80e6-02bb-66e3-9c58-fdddd9ecfee0.png)

## 5. GASを修正
### 5-1. スクリプトを修正 ※1〜4を適宜置換
```js
const slackBotAccessToken = '※1 2-3で発行したSlackBotのアクセストークン';
const spreadSheetId = '※2 3-2でメモっておいたスプレッドシートのID';
const calendarId = '※3 4で作った作ったカレンダーのID@group.calendar.google.com';
const attendees = [ { email: '※4 適当に自分のGsuiteのメアド' } ];

function doPost(e) {
  const slackApp = SlackApp.create(slackBotAccessToken);
  const contents = JSON.parse(e.postData.contents);

  // slackの3秒タイムアウトリトライ対策
  let cache = CacheService.getScriptCache();
  if (cache.get(contents.event.client_msg_id) == 'done') {
    return ContentService.createTextOutput();
  } else {
    cache.put(contents.event.client_msg_id, 'done', 600);
  }

  // スレッドの中からだったらスレッドに返信する
  const opt = contents.event.thread_ts ? { thread_ts: contents.event.thread_ts } : {};
  slackApp.postMessage(contents.event.channel, buildResponseMessage(contents.event.user), opt);

  // 最初の認証でchallengeを返すのに必要だった
  return ContentService.createTextOutput(JSON.stringify(e));
}

// select or upsert してメッセージ返す
function buildResponseMessage(userId) {
  const sheet = SpreadsheetApp.openById(spreadSheetId).getSheets()[0];
  let url = '';
  
  // selectして
  url = selectUrl(sheet, userId);
  // なければ作ってinsert(upsertなのは名残)
  if (!url) {
    url = generateMeetUrl();
    upsertUrl(sheet, userId, url);
  }

  return `${mention(userId)} ${url}`;
}

function getUserIdRange(sheet, userId) {
  let matchedRange = sheet.createTextFinder(userId).matchCase(true).matchEntireCell(true).findNext();

  return matchedRange;
}

// select
function selectUrl(sheet, userId) {
  // userIdで探す
  let matchedRange = getUserIdRange(sheet, userId)
  
  // userIdヒットしなければnull返す
  if (!matchedRange) return null;

  // userIdヒットしてもURLが空文字ならnull返す
  let url = matchedRange.offset(0, 1).getValue();
  if (!url) return null;

  // 前回のアクセス日が空 || 180日以上経過したら無効判定としてURL更新させる
  // meetのURLの有効期限は365日
  let lastDate = matchedRange.offset(0, 2).getValue();
  if (!lastDate || dayDiff(todayYmd(), lastDate) >= 180) return null;

  // 今日の日付で更新
  matchedRange.offset(0, 2).setValue(todayYmd());

  return url;
}

function mention(userId) {
  return `<@${userId}>`;
}

// upsert
function upsertUrl(sheet, userId, url) {
  let matchedRange = getUserIdRange(sheet, userId)

  if (!matchedRange) {
    // なければinsert
    sheet.appendRow([userId, url, todayYmd()]);
    return;
  }

  // あればupdate
  matchedRange.offset(0, 1, 1, 2).setValues([[url, todayYmd()]]);
}

// カレンダー作ってMeetUrlとって消す
function generateMeetUrl() {
  let event = Calendar.Events.insert(
    {
      start: { dateTime: new Date('2020/10/17 10:00:00').toISOString() },
      end: { dateTime: new Date('2020/10/17 12:00:00').toISOString() },
      attendees,
      conferenceData: {
        createRequest: {
          conferenceSolutionKey: {
            type: "hangoutsMeet"
          },
          requestId: 'リクエストID',
        },
      },
    },
    calendarId,
    { conferenceDataVersion: 1 },
  );

  const meetsUrl = event.hangoutLink;
    
  Calendar.Events.remove(calendarId, event.id)
  
  return meetsUrl;
}

function todayYmd() { 
  return toYmd(new Date());
}

function toYmd(date) {
  let y = date.getFullYear();
  let m = ('00' + (date.getMonth()+1)).slice(-2);
  let d = ('00' + date.getDate()).slice(-2);
  return (y + '-' + m + '-' + d);
}

function dayDiff(date1, date2) { 
  return ((new Date(date1) - new Date(date2)) / 86400000);
}
```

### 5-2. SlackAppライブラリを入れる
[Slack BotをGASでいい感じで書くためのライブラリを作った](https://qiita.com/soundTricker/items/43267609a870fc9c7453)
`M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO`

![スクリーンショット 2020-12-12 22.53.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/c29d3011-57d5-a9e9-7bc0-c02dde86fe93.png)

![スクリーンショット 2020-12-12 22.54.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/f9b298f3-d5e5-5b85-6ca6-797f86d5e6d6.png)


### 5-3. Google Calendar のAPIを叩けるようにする
![スクリーンショット 2020-12-13 0.21.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/b0bcb7ce-927b-fc44-1f5e-0ccf377aace8.png)

![スクリーンショット 2020-12-13 0.21.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/b59c7695-cab5-7951-e42f-db0d95783e77.png)


### 5-4. デプロイする
- 最初に仮置きした時と同じ手順で
- `Project version` は `New` を選択してリリース
![スクリーンショット 2020-12-12 21.19.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/0aeb8d21-4018-ad05-e273-5277132dede8.png)

## 6. あとはslackでbotをチャンネルに追加してメンション飛ばせばurlをくれる!
社内でもユーザがだいぶ増えてスプレッドシートはこんな感じになってます
![スクリーンショット 2020-12-13 0.59.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236978/d37e826a-5b10-f76b-fd10-a0fc4e3bdd8a.png)



# TODO
- GASの権限などが実装者(自分)依存になっているので自分が退職してGSuiteアカウント消えても動き続けるようにする必要がある
- がんばればTerraformでコード管理できそうなきがする