[GAS] GoogleAppsScriptでGitHubの通知をSlackのDMに投稿するようにした話

# :star: 追記
公式でほぼ同じ機能が使えるようになったので、こちらの方法がオススメです
["Scheduled reminders" でGitHub上のやり取りをSlackに通知させよう！](https://qiita.com/mikan3rd/items/5ca5c3622f4f7cd5dc70)

# :star: Hello!
**[株式会社Pathee](https://corp.pathee.com/)**のフロントエンド&サーバーサイドエンジニアの@mikan3rdです。
普段は自社サービスの**[Pathee](https://pathee.com/region/)**やCMSの開発を担当しています。

2018年4月より新サービス**[Patheeパートナーツール](https://pathee.com/lp/)**をリリースしたことがきっかけで
社内のGit管理をBitBucketからGitHubに移行しました！

これまでBitBucketのwebhookを使ってPullRequestの通知を行っていたのですが、
GitHubへの移行に伴い、この通知も改善しようということになりました。


# :star: 目標
今回は、前々から触ってみようと思っていた[GoogleAppsScript](https://developers.google.com/apps-script/)を使って
GitHubで`@mention`のついたコメントがあった時、
それぞれのSlackのダイレクトメッセージに送るようにしたいと思います。

<img width="893" alt="スクリーンショット 2018-04-28 19.20.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/8fec0c90-3499-bcef-b398-3758c142d497.png">


# :star: なんで Google Apps Script?
- 無料だから
- サーバーを立てなくていいから
- 社内でGoogleDriveをよく利用しているから（ユーザーが増えたりした時に管理しやすい）
- JavaScriptで書けると思っていたから（ちょっと違った）

# :star: 手順
- GoogleDriveからGASを追加
- GASでアプリケーションのURLを発行
- GitHubでWebhookの設定をする
- GASでpostリクエストを受け取る
- Slackでwebhookの設定をする
- GASからSlackに通知する
- GitHubのユーザーネームをSlackのユーザーネームに変換する
- GitHubのコメントをSlackに通知する


## :pencil: GoogleDriveからGASを追加
- GoogleAppsScriptを初めて使う場合、アプリの追加が必要です
- 新規 => その他 => アプリを追加 を選択

<img width="1027" alt="スクリーンショット 2018-04-27 18.18.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/b1a24158-2739-d70f-c787-44310d752f5c.png">

![drive.google.com_drive_u_0_my-drive(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/4c414075-5eab-559e-dee5-fd20eefd34e4.png)

- 接続を押すと追加されます


## :pencil: GASでアプリケーションのURLを発行
- 新規 => その他 に Google Apps Script が追加されています

<img width="1031" alt="スクリーンショット 2018-04-27 18.44.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/b981a657-ebdb-32a1-fc0b-337c65e56a06.png">

- 公開 => webアプリケーションとして導入 を選択

![script.google.com_a_pathee.com_d_10D49GvMrFM-LVnxaXgHf7UFO_bmWAZmyDizf_BrtZcuA5qEJp8QcIn8V_edit_usp=drive_web&folder=0AEb1iLa_cltmUk9PVA&splash=yes(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/d90b2430-5451-4c7b-2507-47188aa50453.png)

- 「次のユーザーとしてアプリケーションを実行」を選択
- 「アプリケーションにアクセスできるユーザー」で「全員（匿名ユーザーを含む）」を選択

![script.google.com_a_pathee.com_d_10D49GvMrFM-LVnxaXgHf7UFO_bmWAZmyDizf_BrtZcuA5qEJp8QcIn8V_edit_usp=drive_web&folder=0AEb1iLa_cltmUk9PVA&splash=yes(Laptop with MDPI screen) (1).png](https://qiita-image-store.s3.amazonaws.com/0/178077/29d93a63-b3fd-b77f-2823-b0aadf9ad239.png)

- 「現在のウェブアプリケーションのURL」をコピーしておきます

![script.google.com_a_pathee.com_d_10D49GvMrFM-LVnxaXgHf7UFO_bmWAZmyDizf_BrtZcuA5qEJp8QcIn8V_edit_usp=drive_web&folder=0AEb1iLa_cltmUk9PVA&splash=yes(Laptop with MDPI screen) (2).png](https://qiita-image-store.s3.amazonaws.com/0/178077/1ccef8e9-1251-0023-54d3-9b2b50402a91.png)

## :pencil: GitHubでWebhookの設定をする
- 権限が「Owner」の場合「Setttings」が表示されるので選択します
- Webhooksの右上のAdd webhook を選択します

![github.com_organizations_pathee_settings_hooks_new(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/6a03a0cc-546b-88c4-e5e5-fc1b1a500827.png)

- Payload URLに先ほどコピーしたGASのURLを貼り付けます
- Content typeをapplication/jsonに変更します
- Secretは今回は不要です
- 「Send me everything」 または 「Let me select individual events」で必要なイベントを選択します
- 「Add webhook」でGitHubのwebhookの設定は完了です

## :pencil: GASでpostリクエストを受け取る
- GitHubからはmethod=POSTでリクエストが送られてきます
- 参考: [GitHub Webhooks Guide](https://developer.github.com/webhooks/)
- GASでは`doPost`と記述するとpostリクエストを受け取ります
- `JSON.parse(e.postData.getDataAsString())`とすることで受けとったJSONを辞書型objectに変換します
- `console.log()`で[Google Cloud Platformのログ](https://console.cloud.google.com/logs)に出力されます

```js

// POSTリクエストを受け取る
function doPost(e) {
  var data = JSON.parse(e.postData.getDataAsString());

  // Google Cloud Platform のログで確認できる
  // https://console.cloud.google.com/logs
  console.log(data);
}
```
- 試しにGitHub上でコメントなどをしてみると、ログが確認できるはずです

<img width="1230" alt="スクリーンショット 2018-04-27 20.22.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/8f1a3041-67d5-a783-fa36-656a9f058610.png">

## :pencil: Slackでwebhookの設定をする
- Slackのアプリ追加ページでincoming-webhooksを検索し、選択します。
- https://slack.com/apps

![IncomingWebhooks](https://camo.qiitausercontent.com/fb971437d428646b84fc704f976dddf98c3a8e28/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33393733362f35336163353831612d363734632d343433632d373236332d3938356132303135613533382e706e67)

- デフォルトで通知が送られるチャンネルを選択して追加します

![corp-pathee.slack.com_apps_new_A0F7XDUAZ-incoming-webhooks(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/4d181210-c1af-0ef8-f05b-7ef836bb13e1.png)

- 名前やアイコンは自由に設定してください
- Webhook URL をコピーしておきます

![corp-pathee.slack.com_services_BAC351L5U(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/50148c8a-5e8b-f849-0d1c-3bd2858b1c66.png)

## :pencil: GASからSlackに通知する
- slackのwebhookにリクエストを送るfunctionを作ります
- `link_names: 1`することで、text内のユーザーネームをリンク化できます
- `channel: '@username'`で、その人のslackbotにメッセージが届きます
- 新しいユーザーなど、一部の場合は`@username`だと認識されない場合があるようです
- そのユーザーについては`@userID`にするとちゃんと届きます
- 参考：[Slack APIでユーザー宛のメンションができなくなったので対策した](https://qiita.com/momoham/items/ee7bae955efec9db1a96)

```js

var SLACK_WEBHOOK_URL = 'webhookURLを貼り付ける';

// Slackのwebhookにリクエストを送る
function callSlackWebhook() {  
  var params = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({
      channel: '@mikan3rd', // 自分のslackのユーザーネームに変えてください
      text:'テストおおおおおおおおおおおおおおお',
      link_names: 1,
    })
  };
  var response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, params);
  console.log("response:", response);
  return response;
}

````

- GASの上部で実行する関数を選択し、▶︎ボタンを押すと関数が実行され、Slackにメッセージが届くはずです

<img width="470" alt="スクリーンショット 2018-04-27 20.54.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/dea43426-ea35-f213-b8fd-4c7affc429b9.png">


## :pencil: GitHubのユーザーネームをSlackのユーザーネームに変換する
- GitHubのユーザー名をSlackのユーザー名に変換する必要があります
- 次のようにgithubとslackのユーザー名の辞書型objectを用意しましょう
- `@username`では通知できないユーザーは`@userID`も記述します

```js

var USER_NAME_LIST = [
  {github: '@mikan3rd', slack: '@hiroki-ota'},
  {github: '@hoge',     slack: '@fuga',       slackId: '@U3VSP6T0C'}
];
```

- また、GASにはJavascriptのfindメソッドがないため、様々な関数が使えるようになるUnderscoreというライブラリを追加します
- ライブラリはリソース　=> ライブラリ から追加できます
- 「ライブラリを追加」にプロジェクトキー`M3i7wmUA_5n0NSEaa6NnNqOBao7QLBR4j`を入力して追加を押すとライブラリが使えるようになります
- 次のように書くことで、`_`をUnderScoreとして使うことができます

```js

// ライブラリUndserScoreを使えるようにする
var _ = Underscore.load();
```

- GitHubから送られてきたデータにコメントがある場合、コメント内の`@mention`をslackのユーザーネームに置き換える処理を実装します 
- 先ほどの`USER_NAME_LIST`に一致するものがあった場合、userListに追加するようにしました


```js

// ダイレクトメッセージを送るusernameを格納する通知リスト
var userList = [];

// GitHubのコメントをslackに通知する関数
function notifyslack(data) {
  
  // commentがない場合は通知しない
  if (!data.comment) {
    return;
  }

  // コメント内の@mentionをslackのユーザーネームに置き換える
  var comment = data.comment.body.replace(/@[a-zA-Z0-9_\-]+/g, convertAndAddUserName);
  console.log(comment);

}


// GitHubのユーザーネームをSlackのユーザーネームに変換して送信先に追加する関数
function convertAndAddUserName(githubName) {

  // UnderscoreのfindメソッドでUSER_NAME_LISTに対応するものがあるか探す
  var user = _.find(USER_NAME_LIST, function(user) {return user.github === githubName});

  if (user) {

    // slackIDが定義されている場合は優先して使う
    var slackName = user.slackId || user.slack

    // slackの通知リストに追加
    userList.push(slackName);

    // <@username>とするとslackでリンク化される
    return '<' + slackName + '>';
  }

  return githubName;
}

```

## :pencil: GitHubのコメントをSlackに通知する
- これまでの関数をつなげれば、GithubのリクエストをGASで受け取り、ユーザーネームを変換して、slackに通知する機能は完成です

```js
// ライブラリUndserScoreを使えるようにする
var _ = Underscore.load();

var SLACK_WEBHOOK_URL = 'webhookURLを貼り付ける';

var USER_NAME_LIST = [
  {github: '@mikan3rd', slack: '@hiroki-ota'},
  {github: '@hoge',     slack: '@fuga',       slackId: '@U3VSP6T0C'}
];

// ダイレクトメッセージを送るusernameを格納する通知リスト
var userList = [];


// POSTリクエストを受け取る
function doPost(e) {
  var data = JSON.parse(e.postData.getDataAsString());
  notifyslack(data);
}


// GitHubのコメントをslackに通知する関数
function notifyslack(data) {

  // commentがない場合は通知しない
  if (!data.comment) {
    return;
  }

  // コメント内の@mentionをslackのユーザーネームに置き換える
  var comment = data.comment.body.replace(/@[a-zA-Z0-9_\-]+/g, convertAndAddUserName);
  
  // userListのユーザーそれぞれにダイレクトメッセージを送る
  for each(var user in userList) {
    callSlackWebhook(user, comment);
  } 
}


// GitHubのユーザーネームをSlackのユーザーネームに変換して送信先に追加する関数
function convertAndAddUserName(githubName) {

  // UnderscoreのfindメソッドでUSER_NAME_LISTに対応するものがあるか探す
  var user = _.find(USER_NAME_LIST, function(user) {return user.github === githubName});

  if (user) {

    // slackIDが定義されている場合は優先して使う
    var slackName = user.slackId || user.slack

    // slackの通知リストに追加
    userList.push(slackName);

    // <@username>とするとslackでリンク化される
    return '<' + slackName + '>';
  }

  return githubName;
}


// Slackのwebhookにリクエストを送る
function callSlackWebhook(user, comment) {  
  var params = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({
      channel: user,
      text: comment,
      link_names: 1,
    })
  };
  var response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, params);
  console.log("response:", response);
  return response;
}
```

# :star: おまけ
- GitHubから送られてくるactionにはassigned/review_request/approve/mergeなど様々なものがあるので、さらにカスタマイズしていくとこんな感じの賑やかな通知ができます

<img width="886" alt="スクリーンショット 2018-04-28 19.15.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/178077/15b67edb-5cbc-1b1c-cf1d-e67308751d8f.png">

- 参考: [GitHub Webhooks Guide](https://developer.github.com/webhooks/)


# :star: 感想
今回、初めてGASを触ってみたのですが、普段のフロントエンドの開発ではES6で書いているので、

- constが使えない 
- 文字出力がテンプレートリテラルでできない
- アロー関数が使えない

のは違和感がありました。また、

- Javascriptの基本的なメソッド(includesとか)で使えないものがある
- for..inでObjectの繰り返し処理できない
- 正規表現の先読み/後読みが対応していない
- リクエストのheaderが取得できない
- `console.log()`がその場で確認できない(`Logger.log()`を使う)

など、普通のJavaScriptでできることができない部分もありました。

GASはあくまで"JavaScriptっぽく"書ける、ということだったんですね。

それでも、やりたいことは一通り実装することができたので、
Googleスプレッドシートと連携したスクリプトを書きたい時や、
「サーバーレスでちょっとした処理を作りたい!」という時には良いと思います！

# :star: 次回
GASでGitHubのAPIを叩いてレビュアーをランダムで選ぶスクリプトを作ったので紹介しようと思います。