【GAS】Imgurに画像を投稿してくれるLINE Botの作成

# 0. はじめに

「簡単に画像を共有してぇ～」「LINEで画像送ったら画像URL取得できるようにしてぇ～」と思って「Imgur LINE Bot」と検索してもコードが公開されているサイトが見つからなかったので、自分で作ってみました。皆書いてくれ～。

完成イメージ↓

![完成](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/00-01-talk.jpg)

# 1. Imgur

## 1-1. Imgurにログイン

まずはImgurにログインしてください。アカウントを持っていない方は[こちら](https://imgur.com/register)からアカウントを作成してください。

https://imgur.com/

## 1-2. Imgur APIの取得

APIの取得は↓からできます。

https://api.imgur.com/oauth2/addclient

- Application name: アプリケーション名
- Authorization type: ```Anonymous usage without user authorization```を選択
- Authorization callback URL: 匿名アップロードの場合は関係ないので、適当なURLを入力
- Application website (optional): 省略可能
- Email: メールアドレス
- Description: アプリケーションの説明

![API取得](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/01-01-api.png)

Submitを押すと↓のような画面が表示されるので、```Client ID```と```Client secret```をメモしておいてください。(今回使用するのは```Client ID```のみ)

![API表示](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/01-02-key.png)

# 2. LINE

## 2-1. LINE Developersにログイン

↓のサイトの右上の「コンソールにログイン」からログインしてください。

https://developers.line.biz/ja/

ログイン後、このような画面になります。プロバイダーを作成していない方は、「作成」をクリックし、プロバイダーを作成してください。

![プロバイダー](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/02-01-provider.png)

## 2-2. LINE Botの作成

プロバイダーを選んで、「新規チャネル」→「Messaging API」を選択し、チャネルを作成してください。(プロバイダーを新規に作成した場合は「Messaging API」を選択)

- チャネルの種類: ```Messaging API```
- プロバイダー: 任意のプロバイダーを選択
- 会社・事務所の所在国・地域: ```日本```
- チャネルアイコン: 任意の画像をアップロード
  - 自分は「Imgur」でググってImgurのアイコン画像を拾ってきました
- チャネル名: 任意のチャネル名
- 説明: 任意の説明
- 大業種・小業種: テキトーに選択
- メールアドレス: メールアドレス
- プライバシーポリシーURL・サービス利用規約URL: 任意のURL(空欄でOK)

![チャネル作成](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/02-02-channel.png)

作成が終わると↓の画面になるので、作ったチャネルをクリックしてください。

![チャネル一覧](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/02-03-channel-list.png)

## 2-3. Messaging APIの設定

「Messaging API設定」→「チャネルアクセストークン」へ移動し、「発行」をクリックしてください。発行したトークンはメモしておいてください。

![トークン発行](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/02-04-token.png)

「Messaging API設定」→「LINE公式アカウント機能」へ移動し、「応答メッセージ」の横にある「編集」をクリックし、以下のように設定してください。

![応答機能設定](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/02-05-response.png)

あいさつメッセージは任意です。設定したい場合は設定してください。

# 3. GAS

## 3-1. プロジェクトの作成

GoogleドライブからGoogle App Scriptを開いてください。

![GASを開く](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/03-01-open-gas.png)

## 3-2.環境変数の設定

スクリプトにトークンをそのまま貼り付けるとセキュリティ的に非常にマズイので、先程取得したトークンを環境変数として設定します。

"プロジェクトの設定"→"スクリプトプロパティ"→"スクリプトプロパティを追加"を押します。
「プロパティ」と「値」に先ほど取得したトークンを入力し終えたら"スクリプトプロパティを保存"を押してください。(画像の値はサンプルです)

| プロパティ | 値 |
| ---- | ---- |
| IMGUR | ```ImgurのClient ID``` |
| TOKEN | ```Messaging APIのトークン``` |

![環境変数](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/03-02-env.png)

## 3-3.スクリプトの作成

初期のコードを消してから```コード.gs```に以下のコードを貼り付けてください。

```javascript:コード.gs
function doPost(e) {
  // LINEの情報取得
  const json = JSON.parse(e.postData.contents);
  const replyToken = json.events[0].replyToken;
  const message = json.events[0].message;

  // 画像の場合のみ続行
  if (message.type === 'image') {
    // 画像のバイナリデータ取得
    const contentHeaders = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('TOKEN')}`,
    };
    const contentOptions = {
      'method': 'get',
      'headers': contentHeaders,
    };
    const imageBinary = UrlFetchApp.fetch(`https://api-data.line.me/v2/bot/message/${message.id}/content`, contentOptions).getContent();

    // バイナリデータをBase64エンコード
    const imageBase64 = Utilities.base64Encode(imageBinary);

    // Imgurに投稿
    const imgurOptions = {
      'method': 'post',
      'headers': {'Authorization': `Client-ID ${PropertiesService.getScriptProperties().getProperty('IMGUR')}`,},
      'payload': {'image': imageBase64,},
    };
    const imageUrl = UrlFetchApp.fetch('https://api.imgur.com/3/image', imgurOptions);
    const link = JSON.parse(imageUrl.getContentText()).data.link;

    // APIリクエスト時にセットするペイロード値設定
    const payload = {
      'replyToken': replyToken,
      'messages': [{
          'type': 'text',
          'text': link
        }]
    };
    //HTTPSのPOST時のオプションパラメータ設定
    const options = {
      'payload' : JSON.stringify(payload),
      'myamethod'  : 'POST',
      'headers' : {'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('TOKEN')}`},
      'contentType' : 'application/json'
    };
    // LINE Messaging APIリクエスト
    UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', options);
  }
}
```

## 3-4.デプロイ

"デプロイ"→"新しいデプロイを作成"を押します。
デプロイタイプの選択を求められるので、設定マークを押して"ウェブアプリ"を選択してください。

新しい説明文はテキトーに入力し、"アクセスできるユーザー"を"全員"にしてデプロイを押します。

![デプロイ](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/03-03-deploy.png)

初回はデータへのアクセスを許可する必要があるので、"アクセスを承認"を押してください。
Googleアカウントにログイン後、以下のような画面が出てくるので"Advanced"→"Go to <プロジェクト名>(unsafe)"→"Allow"を押してください。

![アクセス承認](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/03-04-access.png)

そしたらデプロイ完了です。URLはコピーしておいてください。

![デプロイ完了](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/03-05-url.png)

# 4. LINE

## 4-1. Webhook URLの設定

LINE Developersに戻って先程作成したチャネルを選択し、「Messaging API設定」→「Webhook設定」→「Webhook URL」へ移動し、先程コピーしたURLを貼り付けてください。更新を押した後、「Webhookの利用」をオンにしてください。
「検証」を押すことで正常に動作するか確認できます。「成功」と表示されればOKです。

![Webhook有効化](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/04-01-webhook.png)

## 4-2. LINE Botの友達登録

LINEアプリで先程作成したLINE Botを友達登録してください。友達登録のQRコードはWebhook設定の上にあります。これで完成です。
友達登録後、画像を送信するとImgurに画像がアップロードされ、そのURLが返信されます。

他に設定したい項目があれば、LINE Developersの「チャネル基本設定」→「基本情報」の「チャネルアイコンとチャネル名は、LINE Official Account Managerで変更できます。」の箇所から設定してください。

![Setting](https://raw.githubusercontent.com/SoniPana/Qiita-Content/main/images/23/08/30/04-02-settings.png)

# 5. コードの解説

一応コードの解説をしておきます。(間違ってたらスマミセン)

## 5-1. LINEの情報取得

```javascript:コード.gs
function doPost(e) {
  // LINEの情報取得
  const json = JSON.parse(e.postData.contents);
  const replyToken = json.events[0].replyToken;
  const message = json.events[0].message;
```

例えばユーザーがLINEで「Hello, world」と送信すると、LINEのBotは以下のようなJSONを受け取ります。

<details><summary>json</summary><div>

```json
{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "type": "message",
      "message": {
        "type": "text",
        "id": "14353798921116",
        "text": "Hello, world"
      },
      "timestamp": 1625665242211,
      "source": {
        "type": "user",
        "userId": "U80696558e1aa831..."
      },
      "replyToken": "757913772c4646b784d4b7ce46d12671",
      "mode": "active",
      "webhookEventId": "01FZ74A0TDDPYRVKNK77XKC3ZR",
      "deliveryContext": {
        "isRedelivery": false
      }
    },

(以下省略)
```

</div></details>

```json```に↑のjsonを格納し、そこから返信用のトークンとメッセージの内容を取り出し、```replyToken```と```message```に格納しています。

参考↓

https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects


## 5-2. 画像のバイナリデータ取得

```javascript:コード.gs
  // 画像の場合のみ続行
  if (message.type === 'image') {
    // 画像のバイナリデータ取得
    const contentHeaders = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('TOKEN')}`,
    };
    const contentOptions = {
      'method': 'get',
      'headers': contentHeaders,
    };
    const imageBinary = UrlFetchApp.fetch(`https://api-data.line.me/v2/bot/message/${message.id}/content`, contentOptions).getContent();
```

```if (message.type === 'image')```とすることで、「送られてきたメッセージが画像の場合のみ実行」するようにしています。

送られてきたメッセージが画像であることを確認後、https\://api-data.line.me/v2/bot/message/{messageId}/content から画像のバイナリデータを取得しています。
```message.id```は先程取得したメッセージのIDです。

参考↓

https://developers.line.biz/ja/reference/messaging-api/#get-content

## 5-3. Imgurに投稿

```javascript:コード.gs
    // バイナリデータをBase64エンコード
    const imageBase64 = Utilities.base64Encode(imageBinary);

    // Imgurに投稿
    const imgurOptions = {
      'method': 'post',
      'headers': {'Authorization': `Client-ID ${PropertiesService.getScriptProperties().getProperty('IMGUR')}`,},
      'payload': {'image': imageBase64,},
    };
    const imageUrl = UrlFetchApp.fetch('https://api.imgur.com/3/image', imgurOptions);
    const link = JSON.parse(imageUrl.getContentText()).data.link;
```

```Utilities.base64Encode(imageBinary)```で先程受け取ったバイナリデータをBase64エンコードしています。

エンコード後、Imgurにアップロードします。Imgurにアップロードする際は、```Client-ID```をヘッダーに入れてリクエストを送信します。
```link```にアップロードした画像のURLが格納されます。

参考↓

https://apidocs.imgur.com/?version=latest#c85c9dfc-7487-4de2-9ecd-66f727cf3139

## 5-4. LINEで返信

```javascript:コード.gs
    // APIリクエスト時にセットするペイロード値設定
    const payload = {
      'replyToken': replyToken,
      'messages': [{
          'type': 'text',
          'text': link
        }]
    };
    //HTTPSのPOST時のオプションパラメータ設定
    const options = {
      'payload' : JSON.stringify(payload),
      'myamethod'  : 'POST',
      'headers' : {'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('TOKEN')}`},
      'contentType' : 'application/json'
    };
    // LINE Messaging APIリクエスト
    UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', options);
  }
}
```

```replyToken```と```link```を```payload```に格納し、```JSON.stringify(payload)```でJSONに変換しています。
変換後、LINE Messaging APIにリクエストを送信しています。

参考↓

https://developers.line.biz/ja/reference/messaging-api/#send-reply-message

# おまけ

「[0. はじめに](#0-はじめに)」の画像のラーメン、めちゃくちゃ美味いです。

https://www.google.com/maps/place/%E7%94%A8%E5%BF%83%E6%A3%92/@35.6965339,139.7559534,18.56z/data=!3m1!5s0x60188c140ef6e2a3:0xdd3fca37f14c881b!4m6!3m5!1s0x60188c140ef3770f:0xeb571b4d215102f8!8m2!3d35.6964099!4d139.7566482!16s%2Fg%2F1tfbc47r?authuser=0&entry=ttu