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, "最新の検出画像を送信します")
を
変えたら今度は動かない
とりあえず機能をシンプルにするため
新しいプロジェクトで画像送信だけのものを作成する