【GAS】Formから送信されGoogleDriveに保存されるファイルを非共有のままslackに投稿する【初心者向け】

# 🧎🏻はじめに
GoogleFormに添付されたファイルはデフォルトでGoogleDriveに保存されますが
> フォーム投稿時の通知としてSlackにテキストと一緒にファイルとして投稿できると見れるといいよね〜（**ただしGoogleDriveの閲覧権限は非共有のままで！**）

という社内の声があったのでやってみました。
「権限を持っている人向けに閲覧用のURLを生成する」という仕様であれば結構サクッと実現できるのですが、（Incoming Webhookなどでできます）
「非共有のまま＆ファイルを取得してファイルとしてSlackにPostする」を実現するのに少し苦戦した＆ドンピシャの記事は見当たらなかったので備忘録も兼ねて記します！
##### ちなみに
非共有のファイルを取得して投稿するなら共有にすればいいじゃん、という声も聞こえそうですが
- 投稿先のチャンネルにいる人ならファイル見れてもOK（むしろ見れた方がいい）
- Googleアカウントを発行するよりもサクッとやりたい

など諸々の理由によりこのような仕様になったのでご容赦ください。笑


# 👥対象読者
上記を実現したい人。
もしくは以下に当てはまる場合も多少の参考になるかも（保証はしません😶‍🌫️）
- GoogleDriveの非共有ファイルをGASで取得したい
- GASでslackにファイルを送信したい

# 🤐説明しないこと
- GoogleFormの作成
- GoogleFormでGASを書く方法 → 参考：[Googleフォームが送信されたときにGASでいろいろする](https://zenn.dev/newt_st21/articles/using-gas-on-submit-google-form)
- SlackAppの細かい仕様　→　参考：[file.upload API](https://api.slack.com/methods/files.upload) / [chat.postMessage API](https://api.slack.com/methods/chat.postMessage)

# ⛳️完成形の定義
説明をわかりやすくするために以下の形を完成形とします。
|こういうGoogleフォームに回答＆送信すると|こういうSlackが投稿されること|
|-|-|
|![スクリーンショット 2023-01-21 17.47.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/e2f6d357-80ce-f8ca-943c-56abf98ff214.png)|![スクリーンショット 2023-01-22 21.26.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/80e4130c-96c6-7fe8-9364-2d0844ffaf1c.png)|
|・テキスト入力欄が一つ<br>・ファイル添付欄が一つ（複数貼付可能）|テキスト部分を投稿し、その投稿に対してスレッドで画像を添付する|

# 👨🏻‍💻やり方
### 全体の流れ
1. [SlackAppを作る【Slack】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#1-slackapp%E3%82%92%E4%BD%9C%E3%82%8Bslack)
2. [チャンネルにAppを追加【Slack】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#2-%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%ABapp%E3%82%92%E8%BF%BD%E5%8A%A0slack)
3. [スクリプトプロパティを追加【GAS】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#3-%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E8%BF%BD%E5%8A%A0gas)
4. [GASを書く【GAS】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#4-gas%E6%9B%B8%E3%81%8Fgas)
5. [トリガーの設定【GAS】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#5-%E3%83%88%E3%83%AA%E3%82%AC%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9Agas)
6. [権限の設定【GAS】](https://qiita.com/tarakish/items/0595d37435d1e2624c0e#6-%E6%A8%A9%E9%99%90%E3%81%AE%E8%A8%AD%E5%AE%9Agas)

分けて説明していくのでよしなに読み飛ばしてください〜
### 1. SlackAppを作る【Slack】
1. https://api.slack.com/apps にアクセス
→「Create New App」
→「From Scratch」
→「AppName」に任意のapp名＆「Pick a workspace to develop your app in:」に追加したいワークスペースを入力
→「Create App」でアプリ作成
2. 「Add features and functionality」で「Permissions」を選択
→ページ中段にある「Scopes」の「Bot Token Scopes」で「Add an Oauth Scope」から
`files.write`と`chat:write`を追加。
    <img width="500" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/8bdd94cf-c8e9-7c72-99cf-4dd95d426c2a.png">
3. サイドバーの「Install App」から「Install to Workspace」
→無事追加されると遷移先画面にトークンが表示されるのでメモ。
※もし表示されてない場合はサイドバーの`InstallApp`から確認してみてください。

### 2. チャンネルにAppを追加【Slack】
チャンネルの設定メニューのインテグレーションから1で作成したAppを追加する
<img width="300" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/5bf1784a-6d8c-3f5e-3ded-e221704978d9.png">

### 3. スクリプトプロパティを追加【GAS】
https://qiita.com/seratch/items/2158cb0abed5b8e12809

上記の記事によると
トークンなどをハードコーディングするとGASのコードをgit管理などする場合に漏洩リスクが高くなるためスクリプトプロパティに保存しましょう（意訳）
とのことなのでそうします。
上記記事ではGASのバージョンを戻して行っていますが、2023/1現在では最新バージョンでも使える様になっています。
`プロジェクトの設定`タブを開くとスクリプトプロパティの項目があるので追加しましょう。
<img width="700" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/d4c7642b-9ee2-2386-0223-a9ae103ba2f3.png">
- SlackAppのトークン　→　「1. slackAppを作る」の3の手順でメモしたトークン
- 投稿したいSlackチャンネルのID　→　投稿したいSlackチャンネルにアクセスし、URLの最後の`/`以下の文字列（おそらく11桁固定）



### 4. GAS書く【GAS】
一旦完成形だけ載せます。（細かい説明は随時追加していきます！）

#### 完成形
```javascript
// 定数読み込み
const token = PropertiesService.getScriptProperties().getProperty("SLACK_BOT_TOKEN");
const channel = PropertiesService.getScriptProperties().getProperty("SLACK_CHANNEL_ID");

// form送信をトリガーに実行する関数
function onFormSubmit(event) {
  var text = '';
  var files = [];
  var items = event.response.getItemResponses();

  for (var i = 0; i < items.length; i++) {
    if (items[i].getItem().getType() == 'FILE_UPLOAD'){
      var fileIds = items[i].getResponse();
      for (var i = 0; i < fileIds.length; i++) {
        var file = DriveApp.getFileById(fileIds[i]).getBlob();  
        files.push(file);
      }
    } else {
      message = items[i].getResponse();
      text += items[i].getItem().getTitle() + ": " + message + "\n";
    }
  }

  postMessageAndFile(channel, text, files);
}

// slackAPIのchat.postMessageを叩く関数
function postMessageToSlackChannel(channel, text) {
  var payload = {
    "token" : token,
    "channel" : channel,
    'text' : text,
  };
  var options = {
    "method" : "post",
    "payload" : payload
  };

  var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", options);
  Logger.log('message:   ' + response);
  const dates = JSON.parse(response.getContentText('utf-8'));
  return dates['ts'];
};

// slackAPIのfiles.uploadを叩く関数
function postFileToSlackChannel(channel, file, thread_ts){
  var payload = {
    "token" : token,
    "channels" : channel,
    'file' : file,
    'thread_ts': thread_ts,
  };
  var options = {
    "method" : "post",
    "payload" : payload
  };

  var response = UrlFetchApp.fetch("https://slack.com/api/files.upload", options);
  Logger.log(response);
};

// テキスト投稿＆ファイル投稿する関数
function postMessageAndFile(channel, text, files) {
  const thread_ts = postMessageToSlackChannel(channel, text);
  for (var i = 0; i < files.length; i++) {
    postFileToSlackChannel(channel, files[i], thread_ts);
  };
};
```

##### 注意点
- **`DriveApp.getFileById`を使う関数の名前を`onFormSubmit()`にすること**
どうやら独自定義した関数名だと`DriveApp.getFileById`が使えない仕様らしく、`onFormSubmit()`など予約された関数内で使う必要があるっぽいので注意。（よく分かってない。嘘言ってるかもしれませんが、この関数名に変えることで使えたのでこうしてます。。。）
参考：[「DriveApp\.getFolderById を呼び出す権限がありません。」の解決について（言語：Google Apps Scritp\)](https://teratail.com/questions/206154)

### 5. トリガーの設定【GAS】
GASのトリガータブを開き、「トリガーを追加」から以下のようにトリガーを設定します。
<img width="500" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/b0023137-e34c-d8c9-6056-d928e4fe6cd3.png">
※エラー通知設定は完成するまでは「今すぐ通知を受け取る」にしておいた方がすぐにエラー内容を確認できるのでデバッグしやすいです。

### 6. 権限の設定【GAS】
作成したGASを実行しようとすると以下のような画面が表示されます。
（以下の画像は`詳細を表示`をクリックした状態）
<img width="500" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/1d75be73-3e47-9038-0df4-b6a34568c751.png
">
`〇〇（安全ではないページ）に移動`を押すことで必要な権限が設定されますが、
心配な場合は以下の記事を参考に手動でも設定できます。
参考：[GAS で OAuth のスコープが足りなくてやったこと](https://zenn.dev/seya/articles/709659242512a7)
追加する権限としては以下の3つがあれば大丈夫なはずです（）
<img width="auto" alt="scopes" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834267/40ac3b0d-ed98-d518-2792-5a3c2362c910.png">

### Done！🎉
お疲れさまでした！
これでフォームに投稿されたテキストおよびファイルがSlackに投稿されるようになっているはず…！
もし実装に詰まった場合は以下「デバッグ時の確認事項」も参考にしてみてください。

# ✅デバッグ時の確認事項
- [x] 変更したGASの保存は行っているか
- [x] SlackAppをチャンネルに追加しているか
- [x] tokenを更新した場合にGASのスクリプトプロパティも更新したか

# ☕️おわりに
2023/1現在時点では基本的に上記手順で行えば実装できるはずですが、
もし詰まった部分や仕様が変わっていてわからない部分、もしくはマサカリ、改善、アドバイスなどあれば
お気軽にコメントいただければと思います！🍻