claspを使ったSlackとGoogle Spreadsheetの連携

今や、slackは情報共有のために広く使われるツールとなり、バグや要望などもslack上で共有されるようになりました。しかし、メッセージツールである以上、投稿を一覧として見ようとすると視覚的にわかりにくかったりするわけです。そこで今回、便利なslackをより有効に活用するため、Google Spreadsheetとの連携について書きたいと思います！

![構成図](https://user-images.githubusercontent.com/48483009/78008332-10c19280-732f-11ea-8af9-fb260ffa1b56.png)


# 対象

-   slack上のメッセージを整理したい人
    
-   slackとGoogleのサービスと連携させたい人

-   GASのプロジェクトをローカルで開発したい人
    
-   こっそり業務改善ツールを作り、評価を上げたい人
    

# 技術的な内容

基本的に、文法や具体的な実装については書いていません。slackのメッセージを抽出し、GASで扱え、その結果としてGoogle Spreadsheetに出力ができるという流れを説明しています。

## バージョン

私が作った時のバージョンを明記します。最新のバージョンはリンク先を参考にしてください。

-   [npm](https://www.npmjs.com/package/npm) : 6.14.4
-   [clasp](https://david-dm.org/google/clasp) : 2.3.0
-   [@types/google-apps-script](https://www.npmjs.com/package/@types/google-apps-script) : 1.0.7
-   [tslint](https://www.npmjs.com/package/tslint) : 5.20.1
    
    > **TSLint is deprecated.**
    > 
    > See this issue for more details: [Roadmap: TSLint → ESLint](https://github.com/palantir/tslint/issues/4534)
    > 
    > 現在、tslintは非推奨となり、ESLintを使うように促されています。ぜひESLintを使ってください。
    
## 環境構築について

今回使うのは、`TypeScript`と[clasp](https://github.com/google/clasp)です。Googleのサービスと連携するのであれば、[GAS](https://developers.google.com/apps-script)を使うのが一般的ですが、~~GASは[ES6](https://www.w3schools.com/js/js_es6.asp)以上に対応しておらず、クラスの記述や変数の宣言が割と不便でした（個人差があります）~~。なので、今回はTypeScriptをコンパイルし、ES6以上の文法をほとんど有しての開発を行えるようにします！

**現在は、GASがV8ランタイムをサポートしたことで、最新のECMAScriptが使えるようになりました。よって、TypeScriptを使う必要もなく、直接スクリプトエディタに書く場合はclaspは必要ありません。その場合は、Slack APIの使い方やスクリプトエディタの使い方だけ参考にしてください。**

私はエディタとしてVSCodeを使っていますが、好きなものを使っていただいて大丈夫です。

> [ 祝！Google Apps Scriptが「V8ランタイム」をサポート！モダンなECMAScript構文が使えるようになった](https://tonari-it.com/gas-v8-runtime/)
>
> [V8 Runtime Overview  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/guides/v8-runtime?hl=ja)

### claspについて

claspとは、コマンドライン上でGASのプロジェクトを開発・管理できるツールです。

> `clasp` is an open-source tool, separate from the Apps Script platform, that lets you develop and manage Apps Script projects from your terminal rather than the Apps Script editor.
> 
> 参考：[Command Line Interface using clasp](https://developers.google.com/apps-script/guides/clasp)

では早速構築していきましょう。

### 構築手順

npmやtslintについて分かる人は、`clasp`と`google-apps-script`を入れるんだな、と思っていただければ十分です。

##### 1. [npm](https://qiita.com/maitake9116/items/7825d90c09f3e2f87dea)をインストールする (参考 [Get npm!](https://www.npmjs.com/get-npm))
    
##### 2. npmでプロジェクトを作り、必要なものをインストールする

 ```bash:terminal
 $ cd PROJECT_LOCATION
 $ npm init $ npm install @google/clasp tslint -D
 $ npm install @types/google-apps-script (-S)
 $ npx tslint --init
 ```
    
 -   `npm init`でプロジェクトを作ります。いろいろ聞かれるので、適宜入力してください。特になければデフォルトでも良いです。

 -   claspとtslintをインストールします。`-D`とは、`--save-dev`と同義で、package.json (npm init時に作られる)のdevDependencies欄にパッケージ名が記入されます。つまり、開発用にインストールしているということです。
     (参考：[npmよく使うコマンドまとめ | Qiita](https://qiita.com/standard-oftware/items/2ac49a409688733c90e7))
 -   google-apps-scriptをインストールします。`-S`とは、`--save`と同義で、package.jsonのdependencies欄にパッケージ名が記入されます。`npm 5.0.0`から、install時にデフォルトでsaveしてくれるので、オプションに指定する必要はありません。
     (参考：[npm-install](https://docs.npmjs.com/cli/install))
 -   [tslint](https://palantir.github.io/tslint/)の基本的な設定をいれます。

この手順では、claspをローカルにインストールしています。お好みでグローバルにしてもらっても構いません（ここではローカルにインストールしているものとします）。そこで、`npm 5.2.0`から、ローカルにインストールしたnpmパッケージを`npx`コマンドだけで実行できるようになったので、これを使います。[npxについて](https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d)
    
##### 3. claspプロジェクトを作る
まず、claspでGoogleアカウントにログインし、それからclaspプロジェクトを作ります。その際に、[Google Apps Scriptの設定画面](https://script.google.com/home/usersettings)を開き、Google Apps Script APIをオンにします。
    
 ```bash:terminal
$ npx clasp login
$ npx clasp create  --rootDir ./src
? Create which script? (Use arrow keys)
❯ standalone 
docs
sheets
slides
forms
webapp
api
```
        
このような画面になり、自分が連携したいサービスを選びます。ちなみに、`--rootDir ./src`と設定することで、`clasp push`実行時に余計なものを読み込まなくてすみます。
        
> 参考：[clasp が Typescript をサポートした！ | Qiita](https://qiita.com/HeRo/items/f2ce057c6b1456e896ad) (大変わかりやすいのでこちらも合わせて)
        
`--rootDir`を付けずに作成した場合は手動で付け加えることができます。`clasp create`したときに、`.clasp.json`というファイルができているので、ここに`rootDir`を付け加えます。scriptIdは、作られたスクリプトエディタ上で、ファイル → プロジェクトのプロパティでもみることができます。
            
```json:.clasp.json
{ 
    "scriptId":"XXXX-XXXXXXXXXXXXXXXXX", 
    "rootDir": "./src"
}
```
            


ここまでで開発をする準備は完了しました。この辺の環境構築がいまいちうまく行かない場合は、[GAS のGoogle謹製CLIツール clasp | Qiita](https://qiita.com/HeRo/items/4e65dcc82783b2766c03) こちらの記事を見ていただくと解決するかもしれません（この記事ではclaspをグローバルでインストールしています）

## Slack APIを使う

ここで、SlackAPIを使ってメッセージを取得する方法について記述します。流れとしては、tokenを取得し、`UrlFetchApp.fetch`を使い`GoogleAppsScript.URL_Fetch.UrlFetchApp`型としてメッセージを取得します。基本的には`JSON`形式です。

### tokenの発行と取得

slack上のチャンネルやメッセージを扱うためには、それ専用のslackアプリを作り、そこでアクセストークンを取得する必要があります。

1. slackアプリを作る
 -   [slack api](https://api.slack.com/) このページへ行き、右上のYour Appsをクリックします
 -   そこで、Create New Appで新しいslackアプリを作成します     
 -   その際、Workspaceによっては権限がない場合があるので、その時は管理者に同意をとってください
2. scopeを指定し、アクセストークンを発行する
OAuth & Permissionsのページにて、必要なOAuth scopeを追加します。
 特定のチャンネルのメッセージを取得するなら、
    - channels : read ([conversations.list method | Slack](https://api.slack.com/methods/conversations.list) )
    - channels : history ([conversations.history method | Slack](https://api.slack.com/methods/conversations.history))

    この二つで十分です。プロジェクトによってはいろいろ追加してください。

3. アプリをWorkspaceにインストールし、アクセストークンを発行する
 - 上の方にある、`Install App`をクリックし、指示に従って進めます
 - アクセストークンを取得します

このトークンは、slackAPIを使う際に必要になります。

### メッセージを取得する

tokenを取得したので、これでslack上のメッセージを取得する準備が整いました。基本的には、[Slack api](https://api.slack.com/) このサイトを参考にしていただければ様々なAPIの使い方が書いてあります。ここでは、一例として`conversations.list`、`conversations.history`と`chat.getPermalink`の使い方を書きます。

#### [conversations.list](https://api.slack.com/methods/conversations.list)

```typescript
const conversationsList = UrlFetchApp.fetch(`https://slack.com/api/conversations.list?token=${TOKEN}&limit=${LIMIT}`);    
```

公式ページからも分かる通り、必須な引数はトークンだけです。他の引数も必要に応じて、`&limit=20`のように付け足していきます。

> This [Conversations API](https://api.slack.com/docs/conversations-api) method returns a list of all [channel-like conversations](https://api.slack.com/types/conversation) in a workspace. The "channels" returned depend on what the calling token has access to and the directives placed in the `types` parameter.
> 
> 意訳
> このAPIはワークスペースにあるチャンネルについていろいろ情報が詰まったオブジェクトのリストを返します。どんなチャンネルを返すかは、tokenや引数にいれるtype パラメータに依存します。

よって、このAPIを使えば、特定のチャンネルのIDが得られます。

#### [conversations.history](https://api.slack.com/methods/conversations.history)

```typescript
const conversationsHistory = UrlFetchApp.fetch(`https://slack.com/api/conversations.history?token=${TOKEN}&limit=${LIMIT}&channel=${CHANNEL_ID}`);
```

必須な引数はトークンとチャンネルIDです。チャンネルIDは`conversations.list`で取得できるので、これを使います(別にlimitはなくてもいいです)。

> This method returns a portion of [message events](https://api.slack.com/events/message) from the specified conversation.
> To read the entire history for a conversation, call the method with no `latest` or `oldest` arguments, and then continue paging using the instructions below.
> 
> 意訳
> このメソッドは特定の会話からメッセージの一部を返します。
> 全ての会話を読み込むには、`latest` や`oldest`といった引数をデフォルトにしておき、下で説明しているページングを使います。

基本的に、historyは量が多く、pagingを使う機会が多いので、

```typescript
const conversationsHistory = UrlFetchApp.fetch(`https://slack.com/api/conversations.history?token=${TOKEN}&limit=${LIMIT}&channel=${CHANNEL_ID}&cursor=${CURSOR}`); 
```

というように、cursorを指定することで次のページを表示できます。`CURSOR`に入るのは、一つ前の`conversations.history`で取得したデータのうち、`response_metadata`ブロックの`next_cursor`の値です。つまり、全ての会話を読み込むには、連鎖的に`conversations.history`メソッドを呼び出す必要があります。

-   注意：`UrlFetchApp.fetch`で返されるデータ型は`GoogleAppsScript.URL_Fetch.UrlFetchApp`型であり、内部のデータにアクセスするにはparseする必要があります。
    
```typescript
JSON.parse(CONVERSATIONS_HISTORY)
```
    

#### [chat.getPermalink](https://api.slack.com/methods/chat.getPermalink)

補足として、メッセージのリンクを取得するメソッドを紹介します。

```typescript
const permalink = UrlFetchApp.fetch(`https://slack.com/api/chat.getPermalink?token=${TOKEN}&channel=${CHANNEL_ID}&message_ts=${MESSAGE_TS}`);
```

このメソッドは、トークンとチャンネルIDとそのメッセージのタイムスタンプが引数として必須です。OAuth scopeは必要ありません。`conversations.history`メソッドでタイムスタンプを取得できるので、合わせて使うと便利になります。

## スクリプトエディタを使う

### pushから実行まで

ローカルでTypeScriptを使い開発した後、スクリプトエディタに出力して関数を実行します。これから流れを書いていきます。

1.  `npx clasp push`
    
    ローカルのコードをスクリプトエディタに出力します。この時、自動的にTypeScriptからGASへコンパイルしてくれます。ちなみに、`npx clasp push --watch`コマンドを入力すると、コードの変更と共にpushしてくれるので、一回一回コマンドを入力する手間が省けます。
    
2.  関数を実行します
    
    関数を選択し、実行します。ブラウザによっては違うかもしれませんが、macでfirefoxを使っている場合、`command + R`で実行できます。ちなみに、ログは`command + enter`で見ることができます。
    

### スクリプトエディタ上での環境変数

github等でコードを共有する場合、tokenは隠しておきたいと思うはずです。そんな時のために、スクリプトエディタは環境変数を用意しています。

ファイル → プロジェクトのプロパティ → スクリプトのプロパティで、プロパティと値を指定します。使い方は、

```typescript
PropertiesService.getScriptProperties().getProperty("property_name");
```

となります。

### ライブラリの使用

GASのプロジェクトで、なんらかのライブラリを使いたい場合は、リソース → ライブラリで、ライブラリのIDを入力します。例えば、日時をうまく扱ってくれるmoment.jsを入れたい場合、`MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48`を入れ、追加します。そして、エディタ上で、`Moment.moment()`といったように使います。moment.jsをGASで書いてくれた人には感謝しかありません。一般的に、`LibraryName.method()`のように使います。

この方法だと、ローカルからpushするたびに再設定する必要があるので、より良い方法は、`appsscript.json`の`dependencies`に書き込むことです。以下のように書くことで、MomentがGASで使えるようになります。

```json:appscript.json
"dependencies": {
    "libraries": [
        {
            "userSymbol": "Moment",
            "libraryId": "MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48",
            "version": "9"
        }    
    ]  
},
```

### トリガーについて

GASのプロジェクトでは、Googleのサーバー上で動かすことができるので、様々なトリガーを使って定期実行や時間指定での実行が可能です。

プロジェクトページにて、プロジェクトの詳細から、新しいトリガーを追加することができます。条件をある程度柔軟に決められるので、非常に便利です。

## Google Spreadsheetへ

ここまできたら、あとは好きなようにできます。Google Spreadsheetと連携している場合、[Spreadsheet Service](https://developers.google.com/apps-script/reference/spreadsheet)を参照して、シートやセルを扱うことができます。そして、抽出したメッセージを綺麗に並べたり、日付を付けたり、リンクを貼ったりすることができます。

[【保存版】初心者向け実務で使えるGoogle Apps Script完全マニュアル](https://tonari-it.com/google-apps-script-manual/)この記事を見ていただくと、Spreadsheet Serviceのオブジェクトの使い方だけでなく、他のGoogleのサービスとの連携に関しても書いてありますので、slackのメッセージをGoogle Calendarに追加するなどさらに応用ができると思います。もちろん、[公式リファレンス](https://developers.google.com/apps-script/reference)も参照してください。

ぜひいろいろなアプリを作ってみてください！