LINEBotに送った画像をGoogleDriveに保存して共有URLを返すまで

##はじめに
GoogleDriveAPIを触っていたので、LINEBotに送信した画像を代わりにアップロードし、共有URLを返して欲しくなったので実装してみました。

##どうなりたいか

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LINEBotに送った画像をGooleDriveにアップロードし、共有URLを返してくれるDEMO <a href="https://twitter.com/hashtag/lineapi?src=hash&amp;ref_src=twsrc%5Etfw">#lineapi</a> <a href="https://t.co/eHoLXtrQvb">pic.twitter.com/eHoLXtrQvb</a></p>&mdash; ようかん / Yosuke Inoue @LAE (@inoue2002) <a href="https://twitter.com/inoue2002/status/1352787370077130753?ref_src=twsrc%5Etfw">January 23, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

単純に画像URLが欲しい時に使う感じです。(パソコンだとなんとでもなるけど、スマホ単体の時には結構助かる)


##仕組み
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/4a0fedc7-4100-1752-0167-f7abe057f7e7.png' width=80%>


###①画像を受け取るLINEBotを実装する
LINEBotでユーザーから画像メッセージを受け取ると以下のようなJSONが送られてきます。

```
 {
  "type": "message",
  "replyToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "source": { "userId": "123456789abcdefghijk", "type": "user" },
  "timestamp": 1611364562326,
  "mode": "active",
  "message": {
    "type": "image",
    "id": "13429433729723",
    "contentProvider": {
      "type": "line"
    }
  }
}
```

LINE Messaging APIでは直接画像データが送られてくる訳ではないので注意が必要です。
JSONの中の`message`プロパティの中にある`id`を使ってコンテンツ取得のAPIを叩くことでデータを取得することができます。
※画像の他にも動画や音声ファイルなども同じように`id`が送られてきます。
詳しくは公式のリファレンスの[こちら](https://developers.line.biz/ja/reference/messaging-api/#message-event)をご覧ください。

###②メッセージIDから画像データを取得する
LINE Messaging APIにはユーザーが送ってきたデータをコンテンツ取得のAPIを利用して扱うことができます。詳しくは公式リファレンスはの[こちら](https://developers.line.biz/ja/reference/messaging-api/#get-content)をご覧ください。

nodejsだと以下のように利用できます（公式リファレンス引用）

```index.js
const line = require('@line/bot-sdk');

const client = new line.Client({
  channelAccessToken: '<channel access token>'
});

client.getMessageContent('<messageId>')
  .then((stream) => {
    stream.on('data', (chunk) => {
      ...
    });
    stream.on('error', (err) => {
      // error handling
    });
  });
```
###③サービスアカウントを作成する
まず、サービスアカウントというものを作成しなければいけません。
[LambdaからgoogleDriveAPIを操作する](https://qiita.com/inoue2002/items/f8d0a270d1457e687432)こちらの記事の通りにサービスアカウントを作成し、連携することで、google APIを利用することができるようになります。

###④サービスアカウントにGoogle Driveのファイルを共有する
画像をアップロードする権限を付与しなければいけません。
サービスアカウントを作成したときにダウンロードするJSONの中にあった、`client_email`のメールアドレスをコピーし、画像をアップロードしたいDriveのファイルをそのメールアドレスに共有します。
▼GoogleDriveでフォルダ共有の方法

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/823e3d44-42fa-fbfd-71b0-afd4d2bbd243.png' width=60%>
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/dca07942-61ea-af7f-34d5-a6bf73f4c542.png' width=60%>

###⑤フォルダIDを取得する
先ほどサービスアカウントに共有したフォルダを開き、そのフォルダのIDを取得します。

```
https://drive.google.com/drive/u/1/folders/xxxxxxxxxxxxxxxxxxxxxxxx
```
フォルダを開いた状態の時のurlを確認し、/folders以降の`xxxxxxxxxxxxxxxxxxxxxxxx`をコピーします。

###⑥Google APIでGoogle Driveにアップロードする

画像をアップロードする部分は以下のように書くことができます。

```index.js
const drive = google.drive({ version: "v3", auth: jwtClient });

//アップロードする画像のデータを取得
const imageStream = await client.getMessageContent(event.message.id);

var fileMetadata = {
  //nameはDriveに保存する時のファイル名になります
  name: `photo.jpg`,
  parents: ["<先ほど取得したフォルダID>"],
};
var media = {
  mimeType: "image/jpeg",
  body: imageStream,
};
await drive.files.create(
  {
    resource: fileMetadata,
    media: media,
    fields: "id",
  },
  function (err, file) {
    if (err) {
      // Handle error
      console.error(err);
    } else {
      //file.data.idをアップロードされたファイルID
      resolve(file.data.id);
    }
  }
);
```
###⑦返ってきたファイルIDを元に画像URLを作る
`https://drive.google.com/uc`にパラメータとして先ほど取得したファイルIDをつけることで画像URLが完成します。

```
ex)
https://drive.google.com/uc?id=1T4QABnxnU_gNt6ORxNvnKU9EiiFxn795
```

###⑧応答するメッセージを作成(リファレンス引用)
応答メッセージの記述も公式リファレンスを参考にしながら書くことができます。[こちら](https://developers.line.biz/ja/reference/messaging-api/#send-reply-message)

```index.js
const line = require('@line/bot-sdk');

const client = new line.Client({
  channelAccessToken: '<channel access token>'
});

const message = {
  type: 'text',
  text: '保存に成功したよ！https://drive.google.com/uc?id=1T4QABnxnU_gNt6ORxNvnKU9EiiFxn795'
};

client.replyMessage('<replyToken>', message)
  .then(() => {
    ...
  })
  .catch((err) => {
    // error handling
  });

```
▼成功したらこんな感じ
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488939/6c41fc7d-8361-81f9-42ad-d8ab5418b6bd.png' width=40%>
　
#最後に
はじめてGoogle Drive APIを触ったついでに作ってみたのですが、やっぱり何かと画像URLを作成してくれるのは楽ですよね。PCがあればそれほど必要ないかもしれないですが、何かと役に立ちますよ。是非皆さんも挑戦してみてください。お疲れ様でした。

▼ちなみにGoogleDriveの反映スピード半端ないです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LINEBot▶️googleDrive　画像のアップロードに成功した<a href="https://twitter.com/hashtag/lineapi?src=hash&amp;ref_src=twsrc%5Etfw">#lineapi</a> <a href="https://t.co/PC59b6CmfG">pic.twitter.com/PC59b6CmfG</a></p>&mdash; ようかん / Yosuke Inoue @LAE (@inoue2002) <a href="https://twitter.com/inoue2002/status/1351808483335700480?ref_src=twsrc%5Etfw">January 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>