Slack / 写真が投稿されたら自動でGoogle Driveに保存する
自分のメモ用にSlackに写真を投稿してますが、
管理しやすいように全ての写真をGoogle Driveに保存しました。
動的に取得したSlackの画像URLを利用して request
モジュールでダウンロードするのですが、 NodeJSのストリームでハマったのでメモします。
Google Drive のフォルダIDをフォルダ名から取得する
Google Drive のフォルダはファイルと同等ですが、フォルダの中にファイルを入れるには
parents
プロパティにフォルダIDを指定します。
まずはそのフォルダIDを取得する必要があります。
SCOPES
で必要なスコープを指定しておきましょう。
Google Cloud Platform で Cloud Functions を新規作成すると自動で Service Account が1つ作成されます。ローカルで開発する場合は、そのアカウントの認証情報をJSONファイルでダウンロードし GOOGLE_APPLICATION_CREDENTIALS
という環境変数にそのパス設定しておきます。そうすると googleapis
が自動で認証情報を読み取ってくれます。
※ GCP上では自動で設定されてますので、環境変数は必要ありません。
また、Service Account には自動でメールアドレスが付与されていますので、
Driveのフォルダの共有設定からメールアドレスを登録しましょう。Service Account はリアルなユーザーではないですが、共有設定をすることで、認証情報を利用すれば自由にアクセスが可能になります。
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const request = require('request');
const { google } = require('googleapis');
const SCOPES = [
'https://www.googleapis.com/auth/drive',
];
const FOLDER_NAME = 'FromSlack';
const auth = await google.auth.getClient({ scopes: SCOPES });
const drive = google.drive({ version: 'v3', auth });
drive.files.list({
corpora: 'allDrives',
includeItemsFromAllDrives: true,
supportsAllDrives: true,
pageSize: 1,
q: `name='${FOLDER_NAME}'`,
fields: 'files(id, name)',
}, (driveError, driveRes) => {
if (driveError) return console.log('The API returned an error: ' + driveError);
const files = driveRes.data.files;
if (files.length) {
const folder = files.find(file => file.name === FOLDER_NAME);
// IDが取得できました。
console.log('Folder id: ', folder.id);
}
}
Slack Event Subscriptions
Slackのメッセージ投稿は監視することができます。
メッセージが投稿されるたびに、指定したURLへPOSTリクエストを投げてくれます。
リクエスト先のURLを設定
Slack API > Event Subscriptions > Request URL
Subscribe to Workspace Events
何のイベントを監視したいかを指定します。
message.channels
を追加しましょう。
このSlackアプリの権限を追加
Slack API > OAuth & Permissions > Scopes
channels:history
メッセージを取得するfiles:read
投稿した写真を取得する
これでSlackにメッセージを投稿する度に Request URL
へPOSTが送られます。
画像URLを取得
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.index = async (req, res) => {
res.sendStatus(200);
const { type, event } = req.body;
const { files } = event;
if (type !== 'event_callback') {
return res.status(500).send('This event is not valid');
}
slackFile = files ? files[0] : undefined;
if (!slackFile) {
return res.status(500).send('No file from Slack');
}
console.log(slackFile.url_private)
}
直後に res.sendStatus(200);
で 200
を返していますが、これをしないとSlackは3回ほどリトライを繰り返します。ちゃんとPOSTがリクエストを受け取ったことをSlackに伝えるための処理です。
参考:
https://stackoverflow.com/questions/50715387/slack-events-api-triggers-multiple-times-by-one-message
画像ファイルのURLは url_private
で取得できます。
画像をダウンロードする
Slackのアクセストークンが必要なので取得しておきましょう。
Slack API > Installed App Settings > OAuth Access Token
encoding: null
を指定して body
を Buffer
で受け取れるようにしています。
この指定がないと body
はテキストとなります。
js
1
2
3
4
5
6
7
8
9
10
11
const request = require('request');
...
const stream = request({
url: slackFile.url_private,
method: 'GET',
encoding: null,
headers: {
'Authorization': `Bearer ${process.env.SLACK_ACCESS_TOKEN}`,
'Content-Type': 'application/json; charset=utf-8',
},
});
request
の返り値は stream
ですが、それをそのまま Google Drive へのアップロードに使えます。 request
だと第2引数のコールバックをよく使用すると思いますが、今回はちょっと違います。
Google Drive にアップロード
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const { PassThrough } = require('stream');
const { google } = require('googleapis');
...
const auth = await google.auth.getClient({ scopes: SCOPES });
const drive = google.drive({ version: 'v3', auth });
...
const stream = request({
...
});
const pass = new PassThrough()
stream.pipe(pass);
drive.files.create({
requestBody: {
parents: [folder.id], // 取得したフォルダID
mimeType: slackFile.mimetype,
name: slackFile.name,
},
media: {
mimeType: slackFile.mimetype,
body: pass,
},
fields: 'id',
}, (fileError, file) => {
if (fileError) {
console.error(fileError);
returnres.status(500).end();
} else {
console.log('File: ', file);
res.status(200).end();
}
});
ここがちょっと変わってるのですが、これがないとファイルの中身が空っぽになってしまいました。
js
1
2
const pass = new PassThrough()
stream.pipe(pass);
参考:
https://stackoverflow.com/questions/19553837/node-js-piping-the-same-readable-stream-into-multiple-writable-targets/40874999#40874999
https://github.com/aws/aws-sdk-js/issues/1277
以上でSlackの投稿を自動でDriveに保存できます。
ユーザーIDやSlackメッセージを検索用フィールドに保存すると、さらに使い勝手が良くなるほと思います。