Slackのバックアップ取得ツールを作ってみた

[2022/9/1からslackのフリープランの変更](https://slack.com/intl/ja-jp/help/articles/7050776459923-%E3%83%97%E3%83%AD%E3%83%97%E3%83%A9%E3%83%B3%E3%81%AE%E6%96%99%E9%87%91%E6%94%B9%E5%AE%9A%E3%81%A8%E3%83%95%E3%83%AA%E3%83%BC%E3%83%97%E3%83%A9%E3%83%B3%E3%81%AE%E6%9C%80%E6%96%B0%E6%83%85%E5%A0%B1#u12501u12522u12540u12503u12521u12531u12398u22793u26356)があります。
そこでslack-apiを使って90日より過去の投稿のバックアップを取得するツールを、Node.jsで作ってみました。

なお、公開チャンネルであれば公式の[エクスポート機能](https://slack.com/intl/ja-jp/help/articles/201658943-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B)があります。


バックアップの対象にするのは以下になります。

- 公開チャンネル・非公開チャンネルへの投稿
- リプライ
- 添付ファイル

**※DMのバックアップは行っていません。**

作成したもののソースはこちらになります。
slackチャンネルの使い方によっては生成されるバックアップのファイルサイズが膨大なものになったりするかもしれません。ツールの実行は自己責任でお願いします。

https://github.com/meisterguild/000-slack-archiver

# 環境情報

- Node.js 16
- slack/web-api 6.7.2

# トークンの取得
https://qiita.com/yoshii0110/items/2a7ea29ca8a40a9e42f4

こちらの記事を参考にトークンを取得します。今回作るツールに必要になるPermissionsのScopeは、

- channels:history
- groups:history
- im:history
- mpim:history
- files:read
- channels:read
- groups:read
- mpim:read
- im:read

これらになります。

# チャンネル一覧の取得

[conversations.list](https://api.slack.com/methods/conversations.list)を叩きます。公開チャンネル、非公開チャンネルの両方を取るようにします。
```javascript
const list = await client.conversations.list({
  cursor: cursor,
  types: "public_channel,private_channel",
});
```
デフォルトでは100件分が取得できます。`cursor`の初期値は`null`で、このAPIの応答に含まれる`next_cursor`を与えることで続きの100件が取得できます。

**※このAPIで取得できるチャンネルはトークンを発行したユーザーの閲覧範囲に限られます。**

# チャンネルの履歴の取得
[conversations.history](https://api.slack.com/methods/conversations.history)を叩きます。
```javascript
const history = await client.conversations
  .history({
    cursor: cursor,
    channel: channelID,
    limit: HISTORY_LIMIT,
  })
```
`cursor`はチャンネル一覧と同様の振る舞いをします。`channel`にはチャンネルのIDを指定します。こちらもデフォルトで100件分の履歴が取得できますが、一度でより多くの履歴を取れるよう`limit`を指定します（最大1000件まで指定できるようです）。

## リプライの取得
以前のAPIではチャンネル履歴の取得で合わせてリプライまで取れていたんですが、今のAPIではリプライが取得できません。
なのであるメッセージの持つ`reply_count`が1以上であれば、[conversations.replies](https://api.slack.com/methods/conversations.replies)を叩いてリプライを取得します。
```javascript
const replies = await client.conversations
  .replies({
    channel: channelID,
    ts: message.ts,
  })
```
こちらを叩くことで`message`に紐づくリプライが全て取得されます。

## 添付ファイルの取得
メッセージに紐づく添付ファイルを取得します。ただしこちらはAPIは提供されていないので、HTTPリクエストを送る必要があります。リクエストするURLはチャンネル履歴やリプライの応答の、`files.url_private_download`になります。
中には`files`が無いメッセージや`url_private_download`が無い添付ファイル情報も含まれているので、そこは条件を書きます。`url_private_download`が無い場合は外部ファイルの埋め込みだったりするので、JSON自体を保存してしまえば良いでしょう。
`url_private_download`をリクエストするには、Bearerトークンで認証する必要があります。
```javascript
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer " + TOKEN,
  },
```
`TOKEN`に指定するトークンは、APIで利用しているものと同じで問題ありません。

### ストレージに保存
ダウンロードしたファイルはストレージに保存します。メッセージと対応付けられるように`message.ts`をディレクトリ名にします。また、添付ファイルは同名のファイルが複数添付されている場合があるので、`files.id`でディレクトリをさらに掘るか、ファイル名に付与するかする必要があります。

# メッセージをJSONファイルとして保存
取得してきたメッセージを一つのArrayに統合し、`JSON.stringfy`でJSON化してストレージに保存します。
一つのチャンネルのメッセージ全てを一つのJSONにしてしまうと、超巨大なJSONファイルとなってしまう可能性があるので、適度に小分けして保存するのが良いでしょう。

# zip化
[archiver](https://www.npmjs.com/package/archiver)を使ってストレージに保存したJSON、添付ファイルを一つのzipファイルにします。

# まとめ
以上がslackのapiを使ったバックアップの取り方でした。大体10000件の投稿があるチャンネルに対して実行すると、約15分かかって約1GBのバックアップが作成されました。ファイルサイズはチャンネルの添付ファイル量が大きく影響すると思います。