Linebot へ画像を送信する その2

Linebot へ画像を送信する

🚀 1. LINE Messaging API の画像送信の条件
LINE の push メッセージや reply メッセージで画像を送信する際、
originalContentUrl と previewImageUrl には 「インターネット上でアクセスできる URL」 を指定する必要があります。
✅ 使用できる URL の条件
1. インターネット上に公開されている
2. https でアクセスできる
3. 画像サイズが 1MB 以下(それ以上は送信できない)
4. MIMEタイプが image/jpeg, image/png, image/gif のいずれか
5. CORS制限がない(Google Drive の「共有リンクを取得」で”リンクを知っている全員が閲覧可能”にする)

ということで
Google drive へ保存し公開にすることで送信できるようにする
既に tmpフォルダを作成済みで
ここへ画像を保存することにする

Google drive API を使うためservice_account.jsonの取得が必要

なお権限の関係上、新しいプロジェクトを作成し
サービスアカウントの作成する

まずプロジェクト名を設定

次にこのプロジェクトに切り替え
Google Drive API を有効化

サービスアカウントを作成
1. 「APIとサービス」 → 「認証情報」 を開く
2. 「+ 認証情報を作成」 → 「サービスアカウント」 を選択
3. サービスアカウントの名前を入力(例: linebot-drive-access)
4. 「作成」ボタンをクリック
5. 「ロールの選択」で編集者を選択

ユーザーにこのサービスアカウントへのアクセスを許可(省略可)
は省力してOK

次に
Google Drive の「共有設定」にサービスアカウントのメールを追加

サービスアカウントのメルアドになるので
これを追加する

次に
サービスアカウントの JSON キー(service_account.json)を取得

APIとサービス」 → 「認証情報」 に戻る
作成したサービスアカウントをクリック
「キー」タブを開く
「鍵を追加」→「新しい鍵を作成」
「JSON」 を選択し、「作成」をクリック
自動的に service_account.json がダウンロードされる

 cp ~/Downloads/linebot-gdrive-fd48d69bd326.json .

でコピー

mv linebot-gdrive-fd48d69bd326.json service_account.json      

でファイル名を変える

次に動作確認

 vim drive_access.py

内容は

from googleapiclient.discovery import build
from google.oauth2 import service_account

# ① サービスアカウントの JSON キーファイル
SERVICE_ACCOUNT_FILE = "service_account.json"
SCOPES = ["https://www.googleapis.com/auth/drive"]

# ② 認証情報をセット
creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
drive_service = build("drive", "v3", credentials=creds)

# ③ Google Drive の temp フォルダの ID
FOLDER_ID = ""

def list_files():
    query = f"'{FOLDER_ID}' in parents and trashed=false"
    results = drive_service.files().list(q=query, fields="files(id, name)").execute()
    return results.get("files", [])

# ④ 画像一覧を取得
files = list_files()
if files:
    for file in files:
        print(f"File: {file['name']}, ID: {file['id']}")
else:
    print("No files found in the Drive folder.")

これで

python drive_access.py 

を実行すると

File: PXL_20240617_182349485.jpg, ID: 

というようにアクセスできるのがわかる

次に画像の送信のテストをする

その前に公開ディレクトリの確認をする

 vim open_folder_check.py

中身は

from googleapiclient.discovery import build
from google.oauth2 import service_account

# 認証情報のセットアップ(Google Drive API 用のサービスアカウント)
SCOPES = ["https://www.googleapis.com/auth/drive.metadata.readonly"]
SERVICE_ACCOUNT_FILE = "path/to/service_account.json"

creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
service = build("drive", "v3", credentials=creds)

# 共有されているフォルダを検索
query = "mimeType='application/vnd.google-apps.folder' and sharedWithMe"
results = service.files().list(q=query, fields="files(id, name, owners, webViewLink)").execute()
folders = results.get("files", [])

if not folders:
    print("公開されているフォルダはありません。")
else:
    for folder in folders:
        print(f"フォルダ名: {folder['name']}, URL: {folder['webViewLink']}, 所有者: {folder['owners'][0]['emailAddress']}")

実行ログから公開されているのが確認できた

GASコードを

const LINEAPI_TOKEN = PropertiesService.getScriptProperties().getProperty('LINEAPI_TOKEN');
const PUSH_URL = 'https://api.line.me/v2/bot/message/push';

/**
 * Webhook (Pythonからリクエストを受け取る)
 * @param {Object} e - HTTP POST リクエスト
 * @returns {ContentService} - 成功・失敗のレスポンス
 */
function doPost(e) {
  try {
    const requestData = JSON.parse(e.postData.contents);
    const userId = requestData.userId;  // 送信先のLINEユーザーID
    const imageUrl = requestData.imageUrl;  // 送信する画像URL
    const message = requestData.message;  // 画像と一緒に送るテキスト

    if (!userId || !imageUrl) {
      throw new Error("userId または imageUrl がありません");
    }

    sendImageToLine(userId, imageUrl, message);
    return ContentService.createTextOutput(JSON.stringify({ status: "success" })).setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    console.error(error);
    return ContentService.createTextOutput(JSON.stringify({ status: "error", message: error.message })).setMimeType(ContentService.MimeType.JSON);
  }
}

/**
 * LINE に画像メッセージを送信
 * @param {string} userId - 送信先の LINE ユーザーID
 * @param {string} imageUrl - 送信する画像URL
 * @param {string} message - 画像と一緒に送るテキスト
 */
function sendImageToLine(userId, imageUrl, message) {
  const payload = {
    "to": userId,
    "messages": [
      {
        "type": "text",
        "text": message
      },
      {
        "type": "image",
        "originalContentUrl": imageUrl,
        "previewImageUrl": imageUrl
      }
    ]
  };

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

  const response = UrlFetchApp.fetch(PUSH_URL, options);
  console.log("LINE送信結果: " + response.getContentText());
}

としてデプロイ

import requests
import random
import time
from googleapiclient.discovery import build
from google.oauth2 import service_account

# ① Google Drive API の設定
SCOPES = ["https://www.googleapis.com/auth/drive"]
SERVICE_ACCOUNT_FILE = "service_account.json"

creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
drive_service = build("drive", "v3", credentials=creds)

# ② 送信先の LINE API 設定
LINE_PUSH_URL = "https://api.line.me/v2/bot/message/push"
LINE_CHANNEL_ACCESS_TOKEN = ""
USER_ID = ""

# ③ Google Drive の「temp」フォルダID
FOLDER_ID = ""

def get_drive_images():
    """
    Google Drive の temp フォルダ内の画像リストを取得し、画像の `id` をリストで返す
    """
    query = f"'{FOLDER_ID}' in parents and mimeType contains 'image/' and trashed=false"
    results = drive_service.files().list(q=query, fields="files(id, name)").execute()
    files = results.get("files", [])

    if not files:
        print("No images found in the Drive folder.")
        return None

    return [file["id"] for file in files]

def get_drive_image_url(file_id):
    """
    Google Drive のファイルIDを画像URLに変換する
    """
    return f"https://drive.google.com/uc?id={file_id}"

def send_image(image_url):
    """
    LINE API を使い、取得した画像URLを LINE ユーザーに送信する
    """
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {LINE_CHANNEL_ACCESS_TOKEN}"
    }

    payload = {
        "to": USER_ID,
        "messages": [
            {
                "type": "image",
                "originalContentUrl": image_url,  # オリジナル画像URL
                "previewImageUrl": image_url  # プレビュー用画像URL
            }
        ]
    }

    response = requests.post(LINE_PUSH_URL, headers=headers, json=payload)
    print(response.status_code, response.text)

def main():
    """
    画像リストを取得し、ランダムな画像を選んでLINEに送信
    """
    image_ids = get_drive_images()
    if not image_ids:
        print("No images found, skipping LINE push.")
        return

    # 画像をランダムに選択
    random_image_id = random.choice(image_ids)
    image_url = get_drive_image_url(random_image_id)

    # LINE に画像を送信
    send_image(image_url)

# スケジュール実行(30分ごと)
if __name__ == "__main__":
    while True:
        main()
        time.sleep(1800)  # 30分ごとに実行

で実行したが
画像が白いので共有フォルダにアクセスできていないらしい

コメントを残す

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