GASへ画像送信機能の追加

GASへ画像送信機能の追加

APIキーはできたので
次にGAS側のコードを書き換える

念の為バックアップをとっておく

var SS = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SS.getSheetByName("問い合わせ内容");
var Trainingsheet = SS.getSheetByName('応答メッセージ');
var Settingsheet = SS.getSheetByName('設定');
var DBsheet = SS.getSheetByName('チャットボット用DB');
var keywordsheet = SS.getSheetByName('キーワード登録済み単語');

// セキュアな方法でアクセストークンを取得
const CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("CHANNEL_ACCESS_TOKEN");

// DBデータを一括取得
var kensaku_words1 = DBsheet.getDataRange().getValues().map(row => row[0]).flat();
var kaitou_array1 = DBsheet.getDataRange().getValues().map(row => row[1]).flat();
var keyword_array1 = keywordsheet.getDataRange().getValues().map(row => row[0]).flat();

function doPost(request) {
  try {
    // POSTデータをパース
    const receiveJSON = JSON.parse(request.postData.contents);

    // イベントがない、または不正な場合は処理しない
    if (!receiveJSON.events || receiveJSON.events.length === 0) {
      return ContentService.createTextOutput("No event").setMimeType(ContentService.MimeType.TEXT);
    }

    const event = receiveJSON.events[0];

    // メッセージがない、またはテキストがない場合は処理しない
    if (!event.message || !event.message.text) {
      return ContentService.createTextOutput("No message text").setMimeType(ContentService.MimeType.TEXT);
    }

    var replytext = "";

    // 検索ワードDBにメッセージがあるかチェック
    var j = kensaku_words1.indexOf(event.message.text);

    if (j !== -1) {
      // 返信メッセージを取得
      replytext = kaitou_array1[j];
    } else if (keyword_array1.includes(event.message.text)) {
      // 登録済みの応答メッセージ
      replytext = "登録済応答メッセージ回答";
    } else {
      // デフォルトメッセージを取得
      replytext = keywordsheet.getRange(2, 5).getValue();
    }

    // LINEに返信
    replyToUser(event.replyToken, replytext);

    // 送信データをスプレッドシートに記録
    sheet.appendRow([getCurrentTime(), event.message.text, replytext]);

    return ContentService.createTextOutput("Success").setMimeType(ContentService.MimeType.TEXT);

  } catch (error) {
    Logger.log("Error in doPost: " + error.toString());
    return ContentService.createTextOutput("Error processing request").setMimeType(ContentService.MimeType.TEXT);
  }
}

// 現在の時間を取得
function getCurrentTime() {
  return Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd HH:mm:ss");
}

// LINEに返信
function replyToUser(replyToken, message) {
  var url = "https://api.line.me/v2/bot/message/reply";
  var payload = {
    "replyToken": replyToken,
    "messages": [{ "type": "text", "text": message }]
  };

  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN
    },
    "payload": JSON.stringify(payload)
  };

  try {
    UrlFetchApp.fetch(url, options);
  } catch (error) {
    Logger.log("Error in replyToUser: " + error.toString());
  }
}

このcode.js を書き換える

config.json に api_key を追加するらしいが
その時に
“gas_webhook_url”: “https://script.google.com/macros/s/XXXXXX/exec”,
が気になったので調べたら
Webhook生成の時のURLで良いらしい

これはデプロイするたびに変わるので設定ファイルに書くのがベスト

そもそもが
originalContentUrl と previewImageUrl には “HTTPSの公開URL” が必要
Base64 で直接送ることはできない
そのため、LINE Bot では画像が表示されない可能性が高い

とのこと
リファレンスを調べた

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

{
  "type": "image",
  "originalContentUrl": "https://example.com/original.jpg",
  "previewImageUrl": "https://example.com/preview.jpg"
}

というように
URLを指定するため画像の保存先が必要らしい

試しにスマホから送信してみたが
スマホから bot への画像送信はできるけど
Bot から画像送信はできなかった

このため保存先が必要

保存先としては
Google Drive や firebaseが候補になるらしい

GASを使用するのと、画像を後々学習に使えそうなので
GoogleDrive へ保存を試すことにする
あとは不要になった時に削除もしやすいのと課金しなくて良い方法を持っておきたいので

GASを使い、LINE Botで送信した画像をOCRし、文字情報をLINE Botへ返信 + GoogleSpreadsheetへ記録する

を参考に行う

Google Drive のmy drive で
LINE_BOT_IMAGES
という新規フォルダを作成

フォルダの値は
https://drive.google.com/drive/folders/
の後の文字列がフォルダIDになるので
GAS の「スクリプトプロパティ」に DRIVE_FOLDER_ID を追加

コード変更してデプロイURLを変更

import requests
import json
import os
from io import BytesIO
from PIL import Image

class LineBotSender:
    def __init__(self, config_path):
        """設定ファイルからLINE Botの情報を読み込む"""
        with open(config_path, 'r') as file:
            config = json.load(file)
        self.channel_access_token = config.get('line_bot_channel_access_token')
        self.channel_secret = config.get('channel_secret')
        self.user_id = config.get('line_bot_user_id')  # ユーザーID
        self.image_file_path = config.get('image_file_path')  # YOLOの検出画像ディレクトリ
        self.api_endpoint = 'https://api.line.me/v2/bot/message/push'

    def send_message(self, message):
        """テキストメッセージを送信"""
        headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {self.channel_access_token}'
        }
        data = {
            "to": self.user_id,
            "messages": [
                {
                    "type": "text",
                    "text": message
                }
            ]
        }
        response = requests.post(self.api_endpoint, headers=headers, json=data)
        if response.status_code != 200:
            raise Exception(f"Error sending message to LINE bot: {response.status_code}, {response.text}")

    def send_image(self, image_path, message="検出された画像を送信します"):
        """画像を送信(3MB超えた場合はリサイズ)"""
        if not os.path.exists(image_path):
            print(f"Error: 画像が見つかりません: {image_path}")
            return
        
        headers = {
            'Authorization': f'Bearer {self.channel_access_token}',
            'Content-Type': 'application/json'
        }

        with open(image_path, 'rb') as img_file:
            image_data = img_file.read()
            image_data = self.resize_image_if_needed(image_data)  # 3MB超えたらリサイズ

            # LINE Bot の API 用データを作成
            payload = {
                "to": self.user_id,
                "messages": [
                    {
                        "type": "text",
                        "text": message
                    },
                    {
                        "type": "image",
                        "originalContentUrl": f"https://your-server.com/images/{os.path.basename(image_path)}",
                        "previewImageUrl": f"https://your-server.com/images/{os.path.basename(image_path)}"
                    }
                ]
            }

            response = requests.post(self.api_endpoint, headers=headers, json=payload)

            if response.status_code != 200:
                raise Exception(f"Error sending image to LINE bot: {response.status_code}, {response.text}")

    def resize_image_if_needed(self, image_data, max_size=3 * 1024 * 1024):
        """画像が max_size (3MB) を超える場合はリサイズ"""
        while len(image_data) > max_size:
            image = Image.open(BytesIO(image_data))
            new_size = (image.width // 2, image.height // 2)  # 縦横 50% 縮小
            image = image.resize(new_size, Image.LANCZOS)

            output = BytesIO()
            image_format = image.format if image.format else 'JPEG'
            image.save(output, format=image_format)
            image_data = output.getvalue()

        return image_data  # 3MB 以下になった画像を返す

    def get_latest_detected_image(self):
        """ `image_file_path` ディレクトリから最新の検出画像を取得 """
        if not os.path.exists(self.image_file_path):
            print("Error: 指定されたディレクトリが存在しません:", self.image_file_path)
            return None

        images = sorted(
            [os.path.join(self.image_file_path, f) for f in os.listdir(self.image_file_path)
             if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
            key=os.path.getmtime, reverse=True
        )

        return images[0] if images else None


if __name__ == "__main__":
    sender = LineBotSender("config.json")  # 設定ファイルのパスを指定
    sender.send_message("こんにちは!")  # LINE Bot で送信

    latest_image = sender.get_latest_detected_image()
    if latest_image:
        sender.send_image(latest_image, "最新の検出画像を送信します")


変えたら今度は動かない

とりあえず機能をシンプルにするため
新しいプロジェクトで画像送信だけのものを作成する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です