Slack Freeプランのストレージ不足とはおさらば！ ～Glitch+GASでファイルをGoogle Driveに転送するアプリを作ろう～

こんにちは、GMAです。

本記事は[過去に書いた記事](https://qiita.com/GMA/items/7e3e7cedcb880f2c1dc9)の改訂版です。
ほぼゼロから作り直したので、新しい記事として投稿します。

(2021/02/27 追記)
「うまく動かないときは」を追加しました。
「応用等」に追記しました。

(2021/06/13 追記)
Gsuite などを利用している場合において認可ができないことがある問題に対応しました。
主な変更箇所は2.1、4.1、4.4、5.1、5.2です。
※設定をやり直される場合は、手順2以降をすべてやり直すことをお勧めします。

#はじめに
コロナの影響もあり、リモートワークの重要性はますます高まっています。リモートワークを効率よく行うには使い勝手の良いコミュニケーションツールが必要です。
そこで、Slack を新たに導入したり、これまでも使っていたけど、これからもっと活用していきたい、と思っている方も多いのではないでしょうか。

Slack にはチームの規模に合わせて複数のプランが用意されていますが、Free プランでも基本的な機能は一通り揃っており大変便利です。
しかし、Slack の Free プランには、 **ストレージがワークスペース全体で5GBしかない** という大きな壁が立ちはだかっているのです…。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/9b528c11-fd9b-e222-31fb-873855f068f4.png)
Slackはファイル共有が手軽に出来て便利であるがゆえ、ある程度人数のいるワークスペースだと、一瞬で容量制限に達してしまうことでしょう。

そこで、今回は Google Apps Script(GAS) を用いて、Slack にアップされたファイルを自動的に Google Drive に移すアプリケーションを作ります。
このアプリで Slack Free プランのストレージ不足とはおさらばです！

[過去記事](https://qiita.com/GMA/items/7e3e7cedcb880f2c1dc9)は実用レベルで使用するには、色々な準備が必要でしたが、今回は GAS と [Glitch](https://glitch.com/) を利用することで、よりセットアップが簡単になりました！

更に過去記事では対応していなかったプライベートチャンネルのファイル転送にも対応しています！

##注意事項

- 実環境で利用する場合はセキュリティに十分注意してご利用ください。 **このアプリケーションを利用して発生した損失や損害に対しては、一切責任を負いかねます。**
- フォルダやトークンは、Google Apps Script のユーザプロパティに「{ユーザID}_TOKEN」という名前で記録しています。 ですから、 **Google Apps Script のプログラムを導入したユーザは、各ユーザがなんのファイルをアップしたか知ることができてしまいます。** Slack のユーザトークンも知り得てしまうので、ワークスペースの利用者はその点に注意してください。
- GAS の仕様の都合上、一度に転送できるファイルサイズの上限は50MBです。それ以上のサイズのファイル転送には使用できませんのでご注意ください。

##仕様
本システムは、Slack と Google Drive を連携させて実現します。
具体的な動作は以下の通り。

1. 画像などのデータを Slack にアップロードすると、EventAPI の通知が飛ぶ
2. Glitch 上のアプリケーションが、Google Apps Script(GAS) のプログラムを起動
3. GAS が Slack 上のファイルを取得する
4. Google Drive にアップロードする
5. アップロードしたファイルの共有リンクを取得する
6. Slack 上の元ファイルを削除する
7. 共有リンクを Glitch のアプリケーションに返却
8. Glitch のアプリケーションが Slack の元ポストを編集して、共有リンクを貼る

![仕様.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/1098718a-7c60-62e5-e8a5-db36e38afe86.png)

(この図を見て、Glitch 不要じゃね？🤔と思った方は鋭いです、、、。実際なくても実現可能ですし、[過去記事](https://qiita.com/GMA/items/7e3e7cedcb880f2c1dc9)では、基本的に GAS のみでやっていますが、Slack の API はそのまま触ろうとするとリクエストやレスポンスの扱い方に結構癖があり、おまけにコロコロ仕様が変わったりするので、GAS から API 叩く必要がないところは、公式の Node.js フレームワークである Bolt が使える Glitch に任せてしまおうという魂胆です)

利用イメージとしては以下の通りです。左が Google Drive の転送前、右が転送後です。
転送後はメッセージ中に Google Drive へのリンクが追加されます。
![利用イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/c52389c4-0597-6ecd-6b27-8d884961c0e0.png)


今回のシステムは **ユーザごとに事前に認可を行う** ことを前提としています。事前に認可を行うことで、ファイルをアップロードされたアカウントで Google Drive のリンクを自動で貼り直すことができるほか、 **プライベートチャンネルにアップロードされたファイルもバッチリ転送できます！**

ちなみに、Google Drive アプリを Slack に導入しておけば、共有リンクを Slack に貼った時に画像等がプレビューされるので (プレビューは Slack のストレージを消費しません)、ほぼこれまで通りの利便性を維持することができます。また、転送しないファイル形式を指定できるので、 **サイズの軽いファイルはこれまで通り slack にアップするだけという使い方も可能です。**

##手順
大まかなセットアップ手順です。

1. Slack 上でアプリケーションを作成する
2. Glitch のファイル転送アプリのコードを Remix する
3. Slack 上でアプリケーションの設定をする
4. Google Drive 上で Google Apps Script を作成し、変数の設定をする
5. Slack、Glitch、Google Drive を連携させるため、色々値を設定する

##1. Slack上でアプリケーションを作成する

https://api.slack.com/apps にアクセスして、アプリを新規作成しましょう。
アプリを作成したら、**Basic Infomation** にある **App Credentials** の情報を後程使用するので、画面はそのまま開いておきましょう。

##2. Glitchのファイル転送アプリのコードをRemixする
Slack と通信する Glitch のアプリケーションを準備します。
このアプリケーションは Slack API を使いやすくするための Node.js フレームワークである Bolt を使用しています。

###2.1. Remix
準備方法はいたって簡単で、 https://glitch.com/edit/#!/remix/slack-file-mover をクリックするだけ！

これだけで、プロジェクトを Remix(コピー) することができます。
(Remixせずにソースだけ見たい方は[こちら](https://glitch.com/edit/#!/slack-file-mover)！)

プロジェクトが新しく出来たら、左上にあるプロジェクト名の部分(下図の赤枠)をクリックします。
すると、プロジェクト名を変更したり、Projectの公開設定を変更したりできます。
Projectを非公開にする場合、必ず`Make This Project Private`にチェックを入れましょう。

![スクリーンショット (163).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/281403ec-467b-5228-0a2a-8c5db580cf21.png)

###2.2. Glitchの環境変数設定①
最低限必要な環境変数の設定を行います。

左のソースコード一覧から.envを選択すると、環境変数の一覧が表示されます。
このうち、Bolt フレームワークを使用するのに、最低限必要な CLIENT_ID と SLACK_SIGNING_SECRET をまず入力します。
それぞれには以下の値を入力してください。

- CLIENT_ID : Slackの Basic Infomation > App Credentials > Client ID
- SLACK_SIGNING_SECRET : Slackの Basic Infomation > App Credentials > Signing Secret

###2.3. 補足
この Glitch プロジェクトでは、以下のことを行っています。

- アプリのメニュー表示
- Slack のファイルアップロードを検知し、GAS にファイルの情報を送り転送してもらう
- GAS から受け取ったファイルへのリンクを Slack に貼る

あとで残りの環境変数も編集を行いますが、Glitch の準備は一旦ここまでです。
プロジェクトの名前は後で使用するので控えておきましょう。

##3. Slack上でアプリケーションの設定をする
###3.1. Event Subscriptions の設定
アプリのメニュー画面を表示するため、 **Event Subscriptions** の設定を行います。
メニュー画面というのは以下のような画面で、今回は認可、認可解除を行うボタンを表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/f0c8741e-906f-64ca-d0fd-acd3b2cd4df7.png)

先程開いていたSlackの画面左にある Event Subscription を選び、Enable Events をONにしてください。
 Request URL という項目が出てきますが、ここには、 **https://{Project名}.glitch.me/slack/events** と入力してください。

正しいURLを入力出来たら、下のように Request URL の右隣に Verified ✔ と表示されます。

![スクリーンショット (160).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/9b709fd8-fec4-1cb4-273d-84e4686838e6.png)

もし、 Verified ✔ と表示されなかったら、以下の2つを確認してください。

- Request URLが正しいかどうか
- Glitchの環境変数 `CLIENT_ID` と `SLACK_SIGNING_SECRET` が正しく入力されているか

URLが入力出来たら、**Subscribe to bot events** の設定を行いましょう。
Add Bot User Event を押して、`app_home_opened`を選択して下さい。
これで、Slack の左のバーから、アプリケーションを選択したときにそのことを検知できるようになります。

次にファイルのアップロードを検知できるようにします。
**Subscribe to events on behalf of users** の設定を行います。
Add Workspace Event を押して、`file_shared`を選択しましょう。

ここまで設定出来たら、忘れずに右下の `Save Changes` を押下してください。

###3.2. Interactivity & Shortcuts の設定
画面左にある **Interactivity & Shortcuts** を押下し、Interactivity をONにしましょう。
Request URL は先程と同様、 **https://{Project名}.glitch.me/slack/events** と入力してください。
この設定は、アプリ画面で **認可/認可解除ボタン** を押された際に応答するために必要です。
終わったらまた右下の `Save Changes` を押下しましょう。

![コメント 2020-07-24 111732.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/e8655342-778f-9e29-5e01-3dbd5373c738.png)

###3.3. OAuth & Permissions の設定
最後に、画面左にある **OAuth & Permissions** から OAuth の設定を行っていきます。
ちなみに OAuth について詳しく知りたい方は、[公式ドキュメント](https://api.slack.com/authentication/oauth-v2)の説明が分かりやすいです。

簡単に説明すると、
1. ユーザに必要な権限をリクエストする
2. ユーザが許可したら、認可サーバに問い合わせてトークンをもらう
3. もらったトークンを使って様々な処理を行う
といった流れです。

では、これらの設定も行っていきましょう。

最初に Bot Token の作成を行います。これはホーム画面の表示に使用する目的で使用します。
**Bot Token Scopes** の Add Permission By Scopes or API Method.. を押下し、`app_mentions:read` を選択します。
ホーム画面表示自体には権限は特に必要ないのですが、最低一つ以上 Bot に権限を与えないと、
Bot Token を作成することができないため、ここでは Bot に対するメンションを拾うことができる権限を便宜上付与しました。
Bot Token Scopes を設定しておくことでアプリのインストール時に Bot Token が発行されます。

Bot Token Scopes の設定が終わったら、その下の User Token Scopes も設定します。
User Token Scopes には以下を設定してください。

- `files:read` ファイルの取得のために必要です。
- `files:write` ファイルのアップロードと削除のために必要です。
- `chat:write` Slackにファイルのリンクを貼るために必要です。
- `channels:history` publicチャンネルのファイルにつけられたコメントを取得するために必要です。
- `groups:history` privateチャンネルのファイルにつけられたコメントを取得するために必要です。  
- `im:history` DMのファイルにつけられたコメントを取得するために必要です。 
- `mpim:history` DMグループのファイルにつけられたコメントを取得するために必要です。
- `users:read` ファイルをアップロードしたユーザ名を取得するために必要です。

以上の設定が終わると以下のような表示になっているかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/61ff418d-2d3a-136f-ff1a-1cff688e4d40.png)

###3.4. アプリのインストール
**OAuth & Permissions** の画面内にある **Install App to Workspace** を押し、アプリをインストールしてください。
これで Bot Token (Bot User OAuth Access Token) が作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/26a661dd-541d-3b63-f330-f1b8621862ae.png)

###3.5. App Home の設定
ホーム画面の設定を行います。画面左にある App Home を選択して下さい。

この画面では、Bot の設定やアプリのホーム画面、チャット画面の表示設定を行うことができます。
 **Show Tabs** にある Home Tab のトグルをONにしてください。これによって、ホーム画面を開くことが可能になります。

なお、その下にある Message Tab はOFFで構いません (BotにDMしたい場合はONにしますが、今回は使用しません)。

![図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/fce12252-900f-e8d2-d278-5be17f3225c7.png)

これで一旦、Slack 上のアプリケーションの設定も終わりです。

##4. Google Drive上でGoogle Apps Scriptを作成し、変数の設定をする
ここから Google Apps Script (GAS) の作成と設定を行います。
GASでは以下のことを行っています。

- 認可、認可解除処理
- ユーザのトークンの管理、保存
- ファイルの転送、削除

本当は Glitch + Bolt ですべて完結できたら楽なのですが、Google Drive の操作とトークンの長期的な保存は GAS のほうがやりやすいので、これらの機能は GAS に任せます。

###4.1. スクリプトの作成
Google Drive を開いて、スクリプトファイルを作成します。「新規 > その他 > アプリの追加」から Google Apps Script を選択して追加しましょう。そして、GAS のファイルを適当な名前で作成したら、以下のコードを入力します。


```javascript:slackFileMover.gs
// DriveAppの権限取得のため、一度実行すること
function Test(){
  DriveApp.getRootFolder().getFiles();
}

// Glitchから呼ばれる
function doPost(e){
  var params = JSON.parse(e.postData.getDataAsString());

  switch (params.func){
    case "authorization":
      return authorization(params.user_id, params.token);
    case "deauthorization":
      return deauthorization(params.user_id);
    case "checkAuthorization":
      return checkAuthorization(params.user_id);
    case "getUserToken":
      return getUserToken(params.user_id);
    case "transferFile":
      return transferFile(params.user_id, params.file_info);
  }
}

// 認可
function authorization(userId, token){
  PropertiesService.getUserProperties().setProperty('TOKEN'+userId, token);
  return ContentService.createTextOutput('{"result": true}');
}

// 認可解除
function deauthorization(user_id) {
  var userProperties = PropertiesService.getUserProperties();
  var token = userProperties.getProperty('TOKEN' + user_id);
  // Slackのユーザトークンの無効化
  try{
    var options = {
      method: 'GET',
      payload: {
        token: token
      }
    }
    UrlFetchApp.fetch('https://slack.com/api/auth.revoke', options);
  } catch (e) {
    return ContentService.createTextOutput('{"result": false}');
  }
  userProperties.deleteProperty('TOKEN' + user_id);      
  return ContentService.createTextOutput('{"result": true}');
}

// ユーザのトークンがあるかどうか
function checkAuthorization(user_id) {
  var userProperties = PropertiesService.getUserProperties();
  var token = userProperties.getProperty('TOKEN'+ user_id);
  var result = (token != null);
  return ContentService.createTextOutput('{"result":'+ result +'}');
}

// ユーザトークンの取得
function getUserToken(user_id) {
  var userProperties = PropertiesService.getUserProperties();
  var token = userProperties.getProperty('TOKEN' + user_id);
  if (token == null) {
    return ContentService.createTextOutput('{"user_token": undefined}');
  }
  return ContentService.createTextOutput('{"user_token": "' + token + '"}');
}

// ファイル転送
function transferFile (userId, fileInfo) {
  // google driveのリンクなら無視
  if(fileInfo.external_type == 'gdrive'){
    return ContentService.createTextOutput('{"isTransfered": false}');
  }
  
  var userProperties = PropertiesService.getUserProperties();
  var scriptProperties = PropertiesService.getScriptProperties();
  
  var token = userProperties.getProperty('TOKEN' + userId);
  if (token == null) {
    return ContentService.createTextOutput('{"isTransfered": false}');
  }

  // ファイルをslackから取得
  var params = {
    method:"GET",
    headers: {
      "Authorization" : "Bearer " + token
    }
  };
  var dlData = UrlFetchApp.fetch(fileInfo.url, params).getBlob();

  // アップロード先のフォルダIDを取得
  var rootFolderId = scriptProperties.getProperty("FOLDER_ID");
  // ファイルをGoogleDriveにアップロード
  var result = uploadFile(rootFolderId, fileInfo.user_name, dlData, fileInfo.mimetype, fileInfo.file_name, token, fileInfo.id, fileInfo.filetype);

  return ContentService.createTextOutput(result);
}

function uploadFile(rootFolderId, userName, dlData, contentType, fileName, token, fileId, filetype){
  // file botに飲み込ませたくないファイルを指定
  var notCopyFileType = ["txt", "text", "applescript", "binary", "space", "c", "csharp", "cpp", "css", "csv", "clojure", "coffeescript", "dart", "d", "erlang", "fsharp", "fortran", "go", "groovy", "handlebars", "haskell", "haxe", "java", "javascript", "kotlin", "latex", "lisp", "lua", "markdown", "ocaml", "xml", "yaml", "pascal", "perl", "powershell", "verilog", "swift", "rtf", "ruby", "rust", "sql", "scala", "post", "php", "python", "vbscript"];
  // txtなどは除外
  for(i in notCopyFileType) {
    if(filetype == notCopyFileType[i]){
      return '{isTransfered: false}';
    }
  }

  // アップロードしたユーザごとにフォルダ分けする
  var folderTarget = DriveApp.getFolderById(rootFolderId);
  var folderID = folderTarget.getFoldersByName(userName +"_slackItems");
  if(!folderID.hasNext()){
    folderTarget = folderTarget.createFolder(userName +"_slackItems");
  } else {
    folderTarget = DriveApp.getFolderById(folderID.next().getId());
  }
  
  // file名の文字化け対策として、名前をセットし直す
  dlData.setName(fileName);
  
  // ContentTypeによっては、slackのプレビューで再生できないので、修正を入れる
  if (contentType == "audio/vnd.wave"){
    dlData.setContentType("audio/wav");
  }

  try {
    // ファイルアップロード
    var driveFile = folderTarget.createFile(dlData);
    
    // 共有設定
    driveFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
    
    // 削除
    deleteFile(token, fileId);
    
    // おまじない
    Utilities.sleep(100);  
    
    return '{"isTransfered": true, "url":"' + driveFile.getUrl() + '"}';  
  }catch(e){
    // おまじない
    Utilities.sleep(100);
    
    if (driveFile == undefined) return '{"isTransfered": false}';
      
    return '{"isTransfered": true, "url":"' + driveFile.getUrl() + '"}';
  }
}

// slackからファイル削除
function deleteFile(token, fileId){
  var options = {
    method: 'POST',
    payload: {
      token: token,
      file : fileId
    }
  };
  // 元ファイルの削除  
  var res = UrlFetchApp.fetch('https://slack.com/api/files.delete',options);
}
```
###4.2. スクリプトの実行
**コードを入力したら保存をしたのち、Test関数を一度だけ実行してください。**

このコードでは DriveApp クラスという Google Drive を操作するクラスを利用している都合、その権限が必要になります。
DriveApp クラスを利用している関数を実行すると、Authorization Required が表示されるので、「許可を確認」をクリックします。
自作アプリなので、そのあと「このアプリは確認されていません」という警告が出ますが、「詳細 > {プロジェクト名}（安全ではないページ）に移動」を選択しましょう(下図参照)。その次の画面では許可を選択してください。

![スクリーンショット (162).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320370/b8faf900-7da7-a7ae-2f62-7329f40a07a3.png)
###4.3. アプリケーションの公開
「公開 > ウェブアプリケーションを導入」を選択しましょう。
各項目を適切に入力し、アプリケーションの実行ユーザは **自分** 、アプリケーションにアクセスできるユーザーは **「全員(匿名ユーザーを含む)」** にしてください。
入力が終わったら「導入」を選択します。

(なお、GAS は **コードを更新するたび、「公開 > ウェブアプリケーションの導入」から更新を行わなければいけない** ので注意しましょう。)

「導入」を選択すると、現在のウェブアプリケーションの URL が表示されますので、これを控えておいてください。

###4.4. スクリプトのプロパティの設定
**「ファイル > プロジェクトのプロパティ > スクリプトのプロパティ」**に必要なプロパティを入力していきます。

- FOLDER_ID : Google Drive のアップロード先のフォルダID

FOLDER_ID は https://drive.google.com/drive/u/0/folders/ の後の文字列です。
GASからファイルをアップロードできるように、適切な共有設定を登録するフォルダに対して行う必要があります( **このアプリケーションを導入したユーザにアップロード権限がなければいけません** )。

※「ファイル > プロジェクトのプロパティ > スクリプトのプロパティ」が見つからない場合は、「以前のエディタを使用」を押して、古いエディタに切り替えてください。

##5. Slack、Glitch、Google Driveを連携させるため、色々値を設定する
だいぶ長い道のりですが、いよいよ最後です。まだ設定しきれていない値を設定していきます。

###5.1. Slackの設定
再びアプリケーションの **OAuth & Permissions** を選択し、Redirect URLs を設定します。
Add New Redirect URL を押下し、 **https://{GlitchのProject名}.glitch.me/authorization** を入力してください。
追加出来たらすぐ下の `Save URLs` を押します。

Redirect URLs を設定した後、左メニューの Manage Distribution のページを開くと、Share Your App with Your Workspace の各項目の値が新たに表示されています。
これらは、アプリケーションをインストールしたり認可したりするときに使うHTMLやURLになります。
今回のアプリケーションでは2つめの Sharable URL を利用しています。

ちなみに、Sharable URL は CLIENT_ID と必要な権限がパラメータとして入っているURLです。 
**Glitch の index.js にこれと同じものをあらかじめ定義してありますので、ユーザに要求する権限を変更したいときは、その部分を編集するようにしてください。**

###5.2. Glitchの環境変数設定②
左のソースコード一覧から.envを選択し、残りの環境変数を埋めていきます。

- SLACK_BOT_TOKEN : OAuth & Permissions > Tokens for Your Workspace > Bot User OAuth Access Token
- GOOGLE_APPS_SCRIPT_URL : GASのウェブアプリケーションURL
- CLIENT_SECRET : Basic Information > App Credentials > Client Secret

以上で実装は終了です！お疲れ様でした！

##実際に使ってみる
Slack の左メニューの下のほうにある Apps の中にインストールしたアプリが入っているはずです。
その中から、今回作成したアプリケーションを開くと、アプリのメニュー画面が開くはずです。
早速認可ボタンを押して認可をしてみましょう。

認可が終わったら適当なチャンネルでファイルを投げてみましょう。
うまくいけば、ファイルが転送されるはずです。

##うまく動かないときは
Slackの画面上などでエラーが出ている場合は、そのエラー内容でググるなどして解決しましょう。
ファイルをアップロードしても何も起こらないなど、エラーを特定できない場合は初めから手順をやり直してみることをお勧めします。
(当たり前ですが、一つでも手順に漏れやミスがあると正常に動作しません。)

<font color="Red">**※筆者多忙のため、原則として不具合報告以外のコメントに返信できません。あらかじめご了承ください。**</font>

##応用等
このアプリケーションを改良すればこんなこともできたりします。
実装方法は割愛しますが、必要に応じて是非実装にチャレンジしてみてください。

- 認可していないユーザに対しては、Bot からメッセージを投げて認可を促す
- ユーザごとに転送先のフォルダを指定できるようにする
- パブリックチャンネルに上がったファイルのみ転送する
- ファイルの種別ごとに転送先を変える

本アプリケーションは Drive Service を利用してファイルのアップロードを行っているので、
下記を参考に slackFileMover.gs を書き換えることで、ファイルアップロード時の挙動も変更できます。

https://developers.google.com/apps-script/reference/drive

#まとめ
GAS と Glitch を用いて、Slack のファイルを Google Drive に転送するシステムを実装しました。
5GBの制限を気にせずに良い、快適な Slack ライフを！

#関連記事
- [【旧版】Slackのストレージを消費せずファイルをアップロード ～アップロードされたファイルを即座にGoogle Driveに転送する～](https://qiita.com/GMA/items/7e3e7cedcb880f2c1dc9)
こちらは過去のバージョンです(一応残していますが、過去のバージョンは原則利用しないでください)。

- [Slackにアップしたファイルを自動でS3にアップするBOTを作ってみた -ファイルはS3にどんどんしまっちゃおうねぇ～-](https://qiita.com/DeployCat/items/c0234b7801bf1204bfff)
過去記事のアイデアを練る際の参考にさせていただきました。
こちらはストレージとしてAmazon S3を利用されていますね。

- https://api.slack.com/tutorials/hello-world-bolt
Slack公式のBoltのチュートリアルです。アプリケーションの実装の参考にしました。