Linebot の画像の管理

Linebot の画像の管理

line へ投稿した画像が保持される期間を調べる

✅ LINE Bot API による画像メッセージの保持期間
項目 内容
originalContentUrl / previewImageUrl 外部URLのため、LINEサーバーには保存されない
LINEサーバー側での画像保存 ❌ なし(開発者が画像URLを提供するだけ)
ユーザー端末での表示保持 一時的にキャッシュされるが、LINE側での保証なし
画像が消える条件 URL先のファイルが削除・非公開になると表示不能になる

originalContentUrl に指定された画像は LINEアプリで直接URLにアクセスして表示しているだけ。
そのため、Google Drive 上の画像を削除すると即時表示できなくなります。
LINE自体には画像が保存されていないため、画像の「保持期間」という概念は存在しません。

既読前に削除されると見えない:「タップしても開けない」状態に。
表示後に削除されると再表示できないこともある:一部キャッシュは残るが再読み込み時にエラーになる可能性。

✅ 安全な運用方針(おすすめ)
* 送信後に数分~数時間だけDriveに画像を保持
→ ユーザーが確実に見られる時間を確保
* ログに送信済みの画像IDと時刻を記録し、後でバッチ削除

とのこと

つまり削除してしまうと閲覧不可能になる

ただ現状の用途は
在庫管理の動作の画像確認
チラシの商品の画像の確認
なので
2日、つまり48時間後には不要になる

このため48時間経過後に削除するようにモジュールを作成する
これでストレージ圧迫を回避できるはず

なお設定時間は今後変更する可能性が高いため
Config.jsonで設定値を変更可能にする

"image_retention_hours": 48  // ← 追加(削除までの保持時間)

の項目を追加

そして削除モジュール

vim delete_old_images.py

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

# 設定ファイルの読み込み
with open("config.json", "r") as f:
    config = json.load(f)

FOLDER_ID = config["google_drive_folder_id"]
RETENTION_HOURS = config.get("image_retention_hours", 48)  # 設定がなければ48時間

# 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)

def get_drive_images(folder_id=FOLDER_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, createdTime)").execute()
    return results.get("files", [])

def delete_old_images(files, hours_threshold):
    now = datetime.datetime.utcnow()
    for file in files:
        created_time_str = file.get("createdTime")
        if created_time_str:
            created_time = datetime.datetime.strptime(created_time_str, "%Y-%m-%dT%H:%M:%S.%fZ")
            delta = now - created_time
            if delta.total_seconds() > hours_threshold * 3600:
                try:
                    drive_service.files().delete(fileId=file["id"]).execute()
                    print(f"✅ Deleted: {file['name']} (created: {created_time})")
                except Exception as e:
                    print(f"⚠️ Error deleting file {file['id']}: {e}")

if __name__ == "__main__":
    files = get_drive_images()
    if not files:
        print("No images found.")
    else:
        delete_old_images(files, hours_threshold=RETENTION_HOURS)

しかし実行すると

⚠️ Error deleting file : <HttpError 403 when requesting https://www.googleapis.com/drive/v3/files/? returned "The user does not have sufficient permissions for this file.". Details: "[{'message': 'The user does not have sufficient permissions for this file.', 'domain': 'global', 'reason': 'insufficientFilePermissions'}]">

となる

これはファイルの所有者が自分のアカウントで
サービスアカウントでアップしたファイルではないため

このためサービスアカウント権限に編集権限を与えるか
サービスアカウントでgoogle drive へアップする必要がある

現在 yolov8 で画像の出力はできているので
サービスアカウントで出力された画像をGoogle Drive へアップロードするモジュールが欲しい

ということで作成

drive_uploader.py

内容を

import os
import json
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from google.oauth2 import service_account

# 設定ファイルの読み込み
with open("config.json", "r") as f:
    config = json.load(f)

FOLDER_ID = config["google_drive_folder_id"]
SERVICE_ACCOUNT_FILE = "service_account.json"
SCOPES = ["https://www.googleapis.com/auth/drive"]

# 認証と Drive API の初期化
creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
drive_service = build("drive", "v3", credentials=creds)

def upload_image_to_drive(filepath, folder_id=FOLDER_ID):
    """
    指定した画像ファイルを Google Drive にアップロードする
    :param filepath: 画像ファイルのパス
    :param folder_id: Drive フォルダ ID
    :return: アップロードされたファイルの ID(失敗時は None)
    """
    if not os.path.exists(filepath):
        print(f"❌ File not found: {filepath}")
        return None

    filename = os.path.basename(filepath)
    file_metadata = {
        "name": filename,
        "parents": [folder_id],
        "mimeType": "image/jpeg"
    }

    media = MediaFileUpload(filepath, mimetype="image/jpeg")

    try:
        file = drive_service.files().create(
            body=file_metadata,
            media_body=media,
            fields="id"
        ).execute()
        print(f"✅ Uploaded to Drive: {filename} (ID: {file['id']})")
        return file["id"]
    except Exception as e:
        print(f"⚠️ Upload failed: {e}")
        return None

def get_public_url(file_id):
    """
    指定されたファイルIDを誰でも見られるように公開設定し、表示用URLを返す
    :param file_id: アップロード済みファイルの ID
    :return: 公開URL(失敗時は None)
    """
    try:
        drive_service.permissions().create(
            fileId=file_id,
            body={"role": "reader", "type": "anyone"},
        ).execute()
        return f"https://drive.google.com/thumbnail?id={file_id}&sz=w1000"
    except Exception as e:
        print(f"⚠️ Failed to make public: {e}")
        return None

として保存

次にアップロードテスト
Yolov8の結果をアップしたいので

from drive_uploader import upload_image_to_drive, get_public_url

file_id = upload_image_to_drive("runs/detect/predict/image1.jpg")
if file_id:
    url = get_public_url(file_id)
    print("✅ 公開URL:", url)

でパスを

../inventory/runs/detect/predict26/image0.jpg 

に変更して実行

なお、コピペするときに改行してしまうと

  File "/Users/snowpool/aw10s/gas_iamge_bot/upload_test.py", line 3
    file_id = upload_image_to_drive("../inventory/runs/detect/predict26/image0.jpg 
                                    ^
SyntaxError: unterminated string literal (detected at line 3)

というようにエラーになるので注意

修正後再度実行すると

python upload_test.py

✅ Uploaded to Drive: image0.jpg (ID: )
✅ 公開URL: https://drive.google.com/thumbnail?id=&sz=w1000

というように成功する

GoogleDrive の tmpフォルダにファイルがあるのも確認

次にこのファイルを削除できるかテストする

ただし48時間経っていないと削除対象にならないためとりあえず保留

48時間経過したので実験

 python delete_old_images.py
✅ Deleted: image0.jpg (created: 2025-03-27 21:04:02.459000)
⚠️ Error deleting file : <HttpError 403 when requesting https://www.googleapis.com/drive/v3/files/? returned "The user does not have sufficient permissions for this file.". Details: "[{'message': 'The user does not have sufficient permissions for this file.', 'domain': 'global', 'reason': 'insufficientFilePermissions'}]">

となって削除完了

コメントを残す

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