Slackのメッセージをchannels.historyで取得した時に取得できるJSONのパターンについて

# はじめに
私が書くとつい長くなってしまうので省略しますが、channels.historyで取得できるJSONのパターンを把握したかったので調べてみました。
ただ私は英語が苦手なので、主に実際のJSONを解析して調べました。
(以前typeだけでなくsubtypeまで存在するあたりで挫折しましたw)

## この記事を読むにあたっての注意点
先ほど書いたように、この記事の内容は私が参加しているチームでの内容を中心に書いています。
不足している情報は公式を参照してください。

- [channels.history method | Slack](https://api.slack.com/methods/channels.history)
- [message event | Slack](https://api.slack.com/events/message)

## ちなみに
Slackでいうchannelはパブリックチャンネルのことで、groupはプライベートチャンネルのことです。
(最初groupをグループメンションのことと勘違いしてしまった。)
groupにもgroups.historyというAPIがあります。
JSONのキーはchannels.historyのと同一です。 (多分)
違いはsubtypeのchannel_*がgroup_*なくらいです。

今回パブリックチャンネルのみを解析しているためchannels.historyでとしましたが
、groups.historyを使っている人も参考になると思います。

# メッセージ用JSONのキーについて

私が参加しているチーム内でいろいろな種類がありそうなチャンネルをいくつか選んで分析を行いました。
その結果19個のキーを確認できました。
全部を網羅しているかわかりませんが、それぞれ概要を書いていこうと思います。

## type
ユーザーの投稿であろうとBOTの投稿であろうとファイル共有であろうと`message`。

## subtype
`メッセージに関するイベント`が入っている。
全部で26もあるので種類ごとに分類。

### ユーザーに関するイベント

種類|内容
:--:|:--:
(キーなし)|メッセージを投稿した
me_message|meコマンドでメッセージを投稿した
message_changed|メッセージを変更した
message_deleted|メッセージを削除した
pinned_item|メッセージかファイルをピン留めした
unpinned_item|メッセージかファイルのピン留めを外した

ちなみに、meコマンドでメッセージを投稿すると_イタリック_になった。
(何のためのコマンドかは不明。)

### BOTに関するイベント
ここでいうBOTはIncomming WebHooks・Outgoing WebHooks・BOT(アカウント)・メッセージを投稿するプログラムなどの事を指す。

種類|内容
:--:|:--:
bot_message|BOTがメッセージを投稿した
bot_add|BOTを追加した
bot_remove|BOTを除外した

### チャンネルに関するイベント

種類|内容
:--:|:--:
channel_name|名前を変更した
channel_topic|トピックを設定・変更した
channel_purpose|目的を設定・変更した
channel_join|ユーザーやBOTアカウントが参加した または 招待された
channel_leave|ユーザーやBOTアカウントが参加をやめた または 除外された
channel_archive|アーカイブされた
channel_unarchive|アーカイブから復元された

### ファイル共有に関するイベント

種類|内容
:--:|:--:
file_comment|ファイルにコメントした
file_mention|共有済みファイルにメンションした
file_share|ファイルを共有した

## user
`ユーザーID`が入っている。
BOTが投稿したメッセージの場合はキー自体ない。

## username
タイプ|内容
:--:|:--
<@ユーザーID|ユーザーアカウント名>|ファイルをアップロードしたユーザーの名前とID
名前|BOTの名前
空文字|OutgoingWebHooksより投稿した時にusernameを指定しなかった？
キーなし|その他メッセージ<br>ユーザーやIncomming WebHooksのメッセージ・チャンネル操作など

## bot_id
BOTに関するメッセージ(追加・削除・投稿)の場合は`BOTのID`が入っている。
ユーザーがファイルアップロードした場合は`空文字`になっている。
それ以外はキーが存在しない。

## text
`メッセージの内容`が入っている。
APIを使って投稿する場合も必須のキーではあるが、`空文字`でもあり。

## ts
UNIX形式の`タイムスタンプ`が入っている。
メッセージの投稿日時に関する情報はこれしかないので、必要なら人が読みやすいように変換する必要がある。

## attachments
`例えばURL入りのメッセージを投稿した時に追加してくれる部分の情報`がJSON形式で入っている。

ちなみに、APIを使って投稿する場合は[これを使うとメッセージをリッチにできる](http://qiita.com/KanaSakaguchi/items/f0b1bb1cf73f0ec5ec71#4-%E3%83%86%E3%82%B9%E3%83%88slack%E3%81%AB%E3%83%AA%E3%83%83%E3%83%81%E3%81%AA%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E6%8A%95%E7%A8%BF%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B)。
(別にリッチってほどではないと思う)

## reactions
`誰がどんなリアクションをしたか`がJSON形式で入っている。
リアクションされていない場合はキーが存在しない。

## edited
`誰がいつ編集したか`がJSON形式で入っている。
この時、textは編集後の内容になっていて編集前の内容は取得できない。
編集されていない場合はキーが存在しない。

## file upload
fileに`共有したファイルの情報やコメント(アップロード時に入力していた場合)など`がJSON形式ではない何かで入っているっぽい。
(JSON形式ではなかったので真面目な解析はやってないです。
[公式情報](https://api.slack.com/types/file)見に行ったらいろいろ情報が載っていたので、気が向いたら別途記事にします。)

アップロードしたファイルの場合uploadがtrueになっている。
Google ドライブのファイルなどのネット上にアップロード済みのファイルの場合はuploadが`空文字`になっている。

それ以外ではfile・uploadともにキーが存在しない。

## display_as_bot
`BOTとして表示させるかどうか`が入っている？
解析結果からは`キーなし`と`空文字`を確認。
'空文字'の場合はfileキーの存在も確認。
一部fileキーあってもこのキーがない場合あり。

[公式情報](https://api.slack.com/types/file)には`false`が入っている例あり。
このキーに関する説明は見つけられていない。

## members
`チャンネルがアーカイブされた時に参加していたメンバー`が配列で入っている。
それ以外はキーが存在しない。

## icons
BOTからの投稿の場合`アイコンに関する情報`がJSON形式で入っている。
それ以外はキーが存在しない。

## topic
`設定・変更したトピックの内容`が入っている。
変更前のトピックの内容はない。

## purpose
`設定・変更した目的の内容`が入っている。
変更前の目的の内容はない。

## is_starred
`スターをつけた場合はTRUE`が入っている。

## item_type
`ピン留めしたメッセージのタイプ？`が入っている。
CとかFとかあったけどよくわからない。

## old_name
`チャンネル名を変更した時の古い名前`が入っている。

## name
`チャンネル名を変更した時の新しい名前`が入っている。

## inviter
ユーザーやBOTをそのチャンネルに招待・追加した人のIDが入っている。

#メッセージ用JSONのキーの組み合わせについて
最後に、見つけられたキーの組み合わせをsubtypeごとに載せてみます。

解析した結果を元にしているため、解析した中に存在しなかったsubtypeについては省略します。
また、ここに載せたsubtypeについても他の組み合わせが存在する可能性もあります。

## どのイベントにも存在するキー
キー|内容
:--:|:--:
type|message
ts|タイムスタンプ

## ユーザーに関するイベント

### (キーなし)
メッセージを投稿した。

キー|内容|備考
:--:|:--:|:--:
user|メッセージを投稿したユーザーのID|
text|投稿したメッセージの内容|
attachments|メッセージに含まれるリンク先の内容(JSON)|URLが含まれる場合のみ
edited|{user=変更したユーザーのID, ts=変更時のタイムスタンプ}|変更済みの場合のみ
reactions|[{"name":"絵文字名","users":["リアクションしたユーザーのID"],"count":リアクション数}]|リアクションされている場合のみ
is_starred|TRUE|スターをつけられた場合のみ

### pinned_item
[pinned_item message event | Slack](https://api.slack.com/events/message/pinned_item)
メッセージかファイルをピン留めした。

キー|内容
:--:|:--:
user|ピン留めしたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> pinned a message to this channel.
item_type|ピン留めしたメッセージのタイプ？　CとかFとかあるけどよくわからない。
attachments|ピン留めしたメッセージかファイルの情報(JSON)

## BOTに関するイベント
ここでいうBOTはIncomming WebHooks・Outgoing WebHooks・BOT(アカウント)・メッセージを投稿するプログラムなどの事を指す。

### bot_message
BOTがメッセージを投稿した。

パターン1：プログラムから投稿したメッセージ

キー|内容
:--:|:--:
username|BOT名
bot_id|BOTID<br>一部ない場合あり？(条件不明)
icons|アイコン情報(JSON)
text|メッセージ
attachments|attachments(JSON) もしくは キーなし

パターン2：連動しているGitHubからのメッセージ

キー|内容
:--:|:--:
bot_id|BOTID
icons|アイコン情報(JSON)
text|メッセージ
attachments|attachments(JSON)

パターン3：Incomming WebHooksのメッセージ

キー|内容
:--:|:--:
username|Incomming WebHooks名
bot_id|BOTID
icons|アイコン情報(JSON)
text|メッセージ
attachments|attachments(JSON) もしくは キーなし

パターン4：Outgoing WebHooksのメッセージ

キー|内容
:--:|:--:
username|username もしくは 空文字
bot_id|BOTID
icons|アイコン情報(JSON)
text|メッセージ
attachments|attachments(JSON) もしくは キーなし

パターン5：RSSのメッセージ

キー|内容
:--:|:--:
username|RSS名
bot_id|BOTID
icons|アイコン情報(JSON)
text|メッセージ
attachments|リンク先の内容(JSON)

### bot_add
BOTを追加した。

キー|内容
:--:|:--:
user|BOTを追加したユーザーのID
text|added an integration to this channel: <インテグレーションのURL&#x7C;インテグレーション名>
bot_id|追加したBOTのID

### bot_remove
BOTを除外した。

キー|内容
:--:|:--:
user|BOTを除外したユーザーのID
text|removed an integration from this channel: <インテグレーションのURL&#x7C;インテグレーション名>
bot_id|除外したBOTのID

## チャンネルに関するイベント

### channel_name
名前を変更した。

キー|内容例
:--:|:--:
user|名前を変更したーユーザーのID
text|<@ユーザーID|ユーザー名> has renamed the channel from "古い名前" to "新しい名前"
old_name|古い名前
name|新しい名前

### channel_topic
トピックを設定・変更した。

キー|内容例
:--:|:--:
user|トピックを設定・変更したユーザーのID
text|"<@ユーザーID&#x7C;ユーザー名> set the channel topic: 設定・変更後のトピック"
topic|設定・変更後のトピック

### channel_purpose
目的を設定・変更した。

キー|内容例
:--:|:--:
user|目的を設定・変更したユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> set the channel purpose: 設定・変更後の目的
purpose|設定・変更後の目的

### channel_join
ユーザーやBOTアカウントが参加した または 招待された。

パターン1：自分からチャンネルに参加した

キー|内容例
:--:|:--:
user|参加したユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> has joined the channel

パターン2：チャンネルに招待された

キー|内容例
:--:|:--:
user|招待されたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> has joined the channel
inviter|招待した人のユーザーID

パターン3：BOTアカウントを追加した

キー|内容例
:--:|:--:
user|追加したBOTアカウントのID
text|<@BOTアカウントのID&#x7C;BOT名> has joined the channel
inviter|追加した人のユーザーID

### channel_leave
ユーザーが参加をやめた。

キー|内容例
:--:|:--:
user|参加をやめたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> has left the channel

### channel_archive
アーカイブされた。

キー|内容例
:--:|:--:
user|アーカイブしたユーザーの名前
members|その時点でチャンネルに参加していたユーザーのID(配列)
text|<@ユーザーID&#x7C;ユーザー名> archived the channel (w/ 参加していた人数 member)

## ファイル共有に関するイベント

### file_mention
共有済みファイルにメンションした。

キー|内容例
:--:|:--:
user|メンションしたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> mentioned a file: <ファイルのURL>
file|メンションしたファイルの情報(JSONではない何か)

### file_share
ファイルを共有した。

パターン1：ファイルをアップロードした

キー|内容例
:--:|:--:
user|ファイルをアップロードしたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> uploaded a file: <アップロードしたファイルに割り当てられたURL|タイトルもしくはファイル名>
file|共有したファイルの情報など(JSON形式ではない何か)

パターン2：ファイルをアップロードすると同時にコメントした

キー|内容例
:--:|:--:
user|ファイルをアップロードしたユーザーのID
text|<@ユーザーID&#x7C;ユーザー名> uploaded a file: <アップロードしたファイルに割り当てられたURL|タイトルもしくはファイル名> and commented: コメント
file|共有したファイルの情報やコメントなど(JSON形式ではない何か)

# あとがき
もともとバックアップの方法について書くつもりでしたが、書いていて内容がうまくまとめられないと思ったのでJSONの解析話だけにしました。
バックアップがJSONの解析含め、いい感じにできるようになったら改めて考えてみます。

もし書いてある内容に間違い等ありましたらコメントや編集リクエストください。
こんなパターンもあったなどの話も歓迎です。

## JSONをキーごとに分解するコード

JSONの分解を行うために作ったコードを置いておきます。
＊コードの書き方は適当です。

```js:JSONをパース.gs
function parse() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getRange("A2:A").getValues();
  //A列にJSONがあり、B列以降に分解する仕様
  var parseData = sheet.getRange("B:Z").getValues();
  data.forEach(function(d, index, list){
    var json = JSON.parse(d[0]);
    Object.keys(json).forEach(function (key) {
      var c = getColNumber(parseData, key);
      parseData[0][c]=key;
      var value;
      if (json[key] == "" || json[key] == null) {
        value='""'; //キーなしとキーあるけど空文字を区別する
      } else if (json[key] != void 0) {
        value = json[key];
      }
      parseData[index+1][c]=value;
    });
  });
  sheet.getRange("B:Z").setValues(parseData);
}

function getColNumber(parseData, key) {
  var cols = parseData[0];
  var c=0;
  while(c<cols.length){
    if(cols[c]==key || cols[c]==''){
      return c;
    }
    c++;
  }
}
```