LINExChatGPT 渾身の一激！画像から得られる教訓を大喜利風にトークグループにPUSH！

トークグループに対しての通知はあまり気にしたことがなかったので、ちょうど別件で作っていたサンプルがあったのでそれを元に。Googleドライブにある画像ファイルをランダムにピックアップし、選ばれた1枚をトークグループに通知してくれます。

渾身の1枚を激写 => 1枚激写 => 一激ということで、定期的にグループに対して通知すれば話が弾むのではないでしょうか！

あと、せっかくなのでChatGPTを絡めて、「画像から得られる教訓を大喜利風」にコメントしてもらおうと思います。


本記事は下記アドベントカレンダーの記事でもあります。

https://qiita.com/advent-calendar/2023/lineapi

# 必要なもの

- LINE Developers Consoleアカウント
- OpenAIアカウント（gpt-4-vision-preview利用）
    - 画像認識を使うには、Usage tier 1担っている必要があります（おそらく）
- Googelアカウント（ドライブ、GAS利用）
    - GASのスクリプト
    - 写真置き場

# LINE Developers Consoleアカウント

プロバイダやチャネルの作成については多数記事がありますので、そちらをご覧ください。
ここで使っているのは、Messaging APIチャネルです。

https://qiita.com/h2m_kinoko/items/4ed881dcfbb8ebc6cb7e

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/0ced93b7-7f81-88b2-5a4b-8704ab5a0338.png)


:::note
チャネルアクセストークンを発行し控えておきます
:::


![スクリーンショット 2023-12-20 7.11.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/58efbff4-7071-98d6-3e49-138441bfe0ea.png)



# グループトークについて

https://developers.line.biz/ja/docs/messaging-api/group-chats/#receiving-webhook-events

こちらに記載がありますが、グループトークルームにBotを追加するとグループIDを取得できるので、それを元にGASからグループトークルームに対して通知を送ることができます。


# グループIDを取得

```
"source": {
    "type": "group",
    "groupId": "Ca56f94637c...",
    "userId": "U4af4980629..."
}
```

:::note
`groupId` が渡されるのでこれを取得しておきます。
:::

グループではなくとりあえず自身に通知して試してみたいときは、「チャネル基本設定」にあるユーザーIDを指定すれば試すことができます。

![スクリーンショット 2023-12-20 7.12.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/6a608c2d-1fe0-fe39-595e-e470ec497c6b.png)


# Googleドライブフォルダ共有

画像が入っているフォルダを共有します。共有しないと、LINEから画像リンクを参照できないため必要となります。「リンクを知っている人全員」「閲覧者」にします。

![スクリーンショット 2023-12-19 16.02.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/43e7ecaa-3540-0a6d-8037-cfa86821a645.png)


:::note
以下URLの最後の部分（folders/以降）が、一意に識別されるIDとなりますので控えておきます。
:::

![url.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/d44fa6a6-f87b-1dd8-e2d1-4b478fdfda49.png)


# ChatGPT

ChatGPTで画像を入力できるようになったので、APIでも同様のことを試してみたく。
画像から得られる教訓を聞いてみた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/aab6c2a2-a632-af1b-d6ae-83f70577749c.png)

### 以下を参考に叩こうとしたが権限がないと！

https://platform.openai.com/docs/api-reference/chat/create

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/419e5a27-fb30-0256-23bf-df4536b4737d.png)

早速叩いてみたところ、以下のエラーがでました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/cc0e96e0-6bd3-e168-94d8-272d3065443c.png)



なにやらGPT4を叩く権限がないとのこと・・・・
形としては、modelに `gpt-4-vision-preview` を指定し、 content に指示と画像URLをセットします。今回、画像URLはGoogleドライブ上の画像にします。

### 色々調べているとどうもAPI利用料で1ドル？以上の請求が発生していないと使えないようです。

以下に記載がありますが、1ドルの支払いがないとGPT4が使えないということで、知らなかった。

https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4

また、アカウントを見ると CURRRENT TIER が Free となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/dadf7b3a-7097-1dbb-5996-9e200dd0ddeb.png)

とりあえず5ドル分のクレジットを購入してみました。世の中やはりお金なのでしょうか。

![スクリーンショット 2023-12-19 12.23.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/48a09f3d-e440-dac2-5632-cff55765d0a2.png)

やはりお金のようです。購入後 Tier1 になってました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/418e15a8-0dda-7e71-ad2b-043f1bcfe468.png)


ということで、これで新たにAPIキーを作成して実行したところ、無事に叩けました！
Googleドライブ上の画像ファイル名、画像、渾身の一激が通知されます。

:::note
APIキーは Tier1 になってから再度作成しないとだめかもしれません。
:::

![IMG_0518.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/ddd40bac-7fd3-f1ab-e21f-9bb82d58559b.png)

![IMG_0519.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/e1a04128-340f-2e5c-aa03-cb8bb2857294.png)


# サンプル

以下、Googleドライブの指定のフォルダ配下にある画像をランダムに1枚選び、それをグループトークルームに通知するサンプルです。ほとんどChatGPTに書いてもらいましたが。GASのトリガーで1日1回などの設定で`sendRandomImageToLine` を叩けば画像と共に通知されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/7049a1ec-b440-9d45-039d-2e304f951ea0.png)


また、スクリプトプロパティに、以下を設定してからデプロイしてください。

| プロパティ | 値 |
|:-:|:-:|
|DRIVE_DIR_ID   |Googleドライブの一意のID   |
|LINE_ACCESS_TOKEN   |MessagingAPIで発行したアクセストークン   |
|OPENAI_KEY   |OPEN AIのキー   |
|TARGET_GROUP_ID   |通知先のトークグループID   |


![スクリーンショット 2023-12-19 15.50.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/c97bd834-dcd1-3a12-b4e4-2fe37cc84581.png)


<details><summary>クリック</summary>

```
const OPENAI_KEY =
  PropertiesService.getScriptProperties().getProperty("OPENAI_KEY");
const LINE_ACCESS_TOKEN =
  PropertiesService.getScriptProperties().getProperty("LINE_ACCESS_TOKEN");

function callChatGptChatCompletionsApi(imageUrl) {
  const apiEndpoint = "https://api.openai.com/v1/chat/completions";
  const payload = {
    model: "gpt-4-vision-preview",
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: `この画像から得られる教訓を大喜利風に解説して。落語家のように400文字以内で大喜利だけを返して`,
          },
          {
            type: "image_url",
            image_url: {
              url: imageUrl,
            },
          },
        ],
      },
    ],
    max_tokens: 500,
  };

  const headers = {
    Authorization: `Bearer ${OPENAI_KEY}`,
    "Content-Type": "application/json",
  };

  const options = {
    method: "post",
    headers: headers,
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(apiEndpoint, options);
  return JSON.parse(response.getContentText());
}

function sendRandomImageToLine() {
  const folderId =
    PropertiesService.getScriptProperties().getProperty("DRIVE_DIR_ID");
  const allFiles = getAllFilesInFolder(folderId);

  if (allFiles.length === 0) return;

  const randomFile = allFiles[Math.floor(Math.random() * allFiles.length)];
  const imageUrl =
    "https://drive.google.com/uc?export=view&id=" + randomFile.getId();
  const fileName = randomFile.getName();

  const chatGptResponse = callChatGptChatCompletionsApi(imageUrl);
  var botResponse = chatGptResponse.choices[0].message.content.trim();

  sendToLine(fileName, imageUrl, botResponse);
}

function getAllFilesInFolder(folderId) {
  const rootFolder = DriveApp.getFolderById(folderId);
  const allJpegFiles = [];

  const processFolder = function (folder) {
    var files = folder.getFilesByType(MimeType.JPEG);
    while (files.hasNext()) {
      var file = files.next();
      allJpegFiles.push(file);
    }

    const subFolders = folder.getFolders();
    while (subFolders.hasNext()) {
      const subFolder = subFolders.next();
      processFolder(subFolder);
    }
  };

  processFolder(rootFolder);

  return allJpegFiles;
}

function sendToLine(fileName, imageUrl, message) {
  const groupId =
    PropertiesService.getScriptProperties().getProperty("TARGET_GROUP_ID");
  const headers = {
    Authorization: `Bearer ${LINE_ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  };

  const payload = {
    to: groupId,
    messages: [
      {
        type: "text",
        text: fileName,
      },
      {
        type: "image",
        originalContentUrl: imageUrl,
        previewImageUrl: imageUrl,
      },

      {
        type: "text",
        text: message,
      },
    ],
  };

  const options = {
    method: "post",
    headers: headers,
    payload: JSON.stringify(payload),
  };

  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", options);
}
```
</details>