From 3d1f901448fcc273b01f2e4d48aaff21d00ddb3a Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 13:10:10 +0900 Subject: [PATCH 01/22] Update app.py --- examples/flask-echo/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 7eeaecaeb..4666a8907 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -95,4 +95,4 @@ def callback(): arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() - app.run(debug=options.debug, port=options.port) + app.run(host="0.0.0.0", debug=options.debug, port=options.port) From 78a5612eeb041d36a3bd20d477370ec3f27de5f9 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 13:20:01 +0900 Subject: [PATCH 02/22] Update app.py --- examples/flask-echo/app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 4666a8907..73038400d 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. - import os import sys from argparse import ArgumentParser @@ -54,7 +53,6 @@ access_token=channel_access_token ) - @app.route("/callback", methods=['POST']) def callback(): signature = request.headers['X-Line-Signature'] @@ -89,10 +87,12 @@ def callback(): if __name__ == "__main__": arg_parser = ArgumentParser( - usage='Usage: python ' + __file__ + ' [--port ] [--help]' + usage='Usage: python ' + __file__ + ' [--debug] [--help]' ) - arg_parser.add_argument('-p', '--port', type=int, default=8000, help='port') arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() - app.run(host="0.0.0.0", debug=options.debug, port=options.port) + # Renderが渡す環境変数PORTを使う(指定がなければ10000を使用) + port = int(os.environ.get("PORT", 10000)) + + app.run(host="0.0.0.0", debug=options.debug, port=port) From c9fb164d8c7e792510bac10e05300edd2893eeb0 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 13:54:38 +0900 Subject: [PATCH 03/22] Update app.py --- examples/flask-echo/app.py | 40 +++++++++----------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 73038400d..0e87e84d7 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -1,32 +1,13 @@ # -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - import os import sys from argparse import ArgumentParser from flask import Flask, request, abort -from linebot import ( - WebhookParser -) -from linebot.v3.exceptions import ( - InvalidSignatureError -) -from linebot.v3.webhooks import ( - MessageEvent, - TextMessageContent, -) +from linebot.v3.webhook import WebhookParser # ✅ 修正 +from linebot.v3.exceptions import InvalidSignatureError +from linebot.v3.webhooks import MessageEvent, TextMessageContent from linebot.v3.messaging import ( Configuration, ApiClient, @@ -48,14 +29,16 @@ sys.exit(1) parser = WebhookParser(channel_secret) +configuration = Configuration(access_token=channel_access_token) -configuration = Configuration( - access_token=channel_access_token -) +# ✅ 動作確認用のルート追加 +@app.route("/") +def index(): + return "LINE Bot is alive." @app.route("/callback", methods=['POST']) def callback(): - signature = request.headers['X-Line-Signature'] + signature = request.headers.get('X-Line-Signature', '') # get request body as text body = request.get_data(as_text=True) @@ -67,7 +50,6 @@ def callback(): except InvalidSignatureError: abort(400) - # if event is MessageEvent and message is TextMessage, then echo text for event in events: if not isinstance(event, MessageEvent): continue @@ -81,10 +63,8 @@ def callback(): messages=[TextMessage(text=event.message.text)] ) ) - return 'OK' - if __name__ == "__main__": arg_parser = ArgumentParser( usage='Usage: python ' + __file__ + ' [--debug] [--help]' @@ -92,7 +72,5 @@ def callback(): arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() - # Renderが渡す環境変数PORTを使う(指定がなければ10000を使用) port = int(os.environ.get("PORT", 10000)) - app.run(host="0.0.0.0", debug=options.debug, port=port) From 7f3618751dbe6dd26b435b807da836acfb2d7568 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 14:31:50 +0900 Subject: [PATCH 04/22] Update app.py --- examples/flask-echo/app.py | 45 ++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 0e87e84d7..eb4014388 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -5,9 +5,13 @@ from argparse import ArgumentParser from flask import Flask, request, abort -from linebot.v3.webhook import WebhookParser # ✅ 修正 +from linebot.v3.webhook import WebhookParser # ✅ 新SDKに対応 from linebot.v3.exceptions import InvalidSignatureError -from linebot.v3.webhooks import MessageEvent, TextMessageContent +from linebot.v3.webhooks import ( + MessageEvent, + TextMessageContent, + ImageMessageContent +) from linebot.v3.messaging import ( Configuration, ApiClient, @@ -18,9 +22,10 @@ app = Flask(__name__) -# get channel_secret and channel_access_token from your environment variable +# チャネルシークレットとアクセストークンを環境変数から取得 channel_secret = os.getenv('LINE_CHANNEL_SECRET', None) channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None) + if channel_secret is None: print('Specify LINE_CHANNEL_SECRET as environment variable.') sys.exit(1) @@ -28,23 +33,22 @@ print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.') sys.exit(1) +# WebhookパーサーとAPIクライアント設定 parser = WebhookParser(channel_secret) configuration = Configuration(access_token=channel_access_token) -# ✅ 動作確認用のルート追加 +# ✅ / にアクセスしたときの簡易応答(Render確認用) @app.route("/") def index(): return "LINE Bot is alive." +# ✅ Webhook受け取りエンドポイント @app.route("/callback", methods=['POST']) def callback(): signature = request.headers.get('X-Line-Signature', '') - - # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) - # parse webhook body try: events = parser.parse(body, signature) except InvalidSignatureError: @@ -53,18 +57,31 @@ def callback(): for event in events: if not isinstance(event, MessageEvent): continue - if not isinstance(event.message, TextMessageContent): - continue + with ApiClient(configuration) as api_client: line_bot_api = MessagingApi(api_client) - line_bot_api.reply_message_with_http_info( - ReplyMessageRequest( - reply_token=event.reply_token, - messages=[TextMessage(text=event.message.text)] + + # ✅ テキストメッセージの場合はそのままオウム返し + if isinstance(event.message, TextMessageContent): + line_bot_api.reply_message_with_http_info( + ReplyMessageRequest( + reply_token=event.reply_token, + messages=[TextMessage(text=event.message.text)] + ) + ) + + # ✅ 画像メッセージの場合は定型メッセージで応答 + elif isinstance(event.message, ImageMessageContent): + line_bot_api.reply_message_with_http_info( + ReplyMessageRequest( + reply_token=event.reply_token, + messages=[TextMessage(text="画像を受け取りました!ありがとう📸")] + ) ) - ) + return 'OK' +# ✅ アプリ起動(Render対応) if __name__ == "__main__": arg_parser = ArgumentParser( usage='Usage: python ' + __file__ + ' [--debug] [--help]' From d6eb346ec590bf0b6f29977dc028e85b45a397af Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:09:19 +0900 Subject: [PATCH 05/22] Update requirements.txt --- examples/flask-echo/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/flask-echo/requirements.txt b/examples/flask-echo/requirements.txt index 4c07bf656..9dd9f100b 100644 --- a/examples/flask-echo/requirements.txt +++ b/examples/flask-echo/requirements.txt @@ -1,2 +1,6 @@ line-bot-sdk flask +google-api-python-client +google-auth +google-auth-httplib2 +google-auth-oauthlib From 7315c931c9a2517bf37cd073a8f3f1899c39d7f7 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:18:43 +0900 Subject: [PATCH 06/22] Update app.py --- examples/flask-echo/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index eb4014388..fde06475d 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -1,5 +1,13 @@ # -*- coding: utf-8 -*- +import base64 +import json +import io +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaIoBaseUpload + + import os import sys from argparse import ArgumentParser From 62abd656f8da03239ffcc3553373ff72fe932b72 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:20:38 +0900 Subject: [PATCH 07/22] Update app.py --- examples/flask-echo/app.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index fde06475d..aec00bc01 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -45,6 +45,37 @@ parser = WebhookParser(channel_secret) configuration = Configuration(access_token=channel_access_token) + +def upload_to_drive(image_bytes, filename): + # 環境変数からBase64の認証情報を復元 + credentials_json = base64.b64decode(os.environ['GOOGLE_CREDENTIALS_BASE64']).decode('utf-8') + credentials_dict = json.loads(credentials_json) + + # 認証情報オブジェクトを作成 + creds = service_account.Credentials.from_service_account_info(credentials_dict) + + # Google Drive APIクライアントを初期化 + service = build('drive', 'v3', credentials=creds) + + # アップロードするファイルの設定 + file_metadata = { + 'name': filename, + 'parents': ['1PoDaKlXm788CXiHeTW9iEFGUwjlxVNBD'] # フォルダIDを指定 + } + + media = MediaIoBaseUpload(io.BytesIO(image_bytes), mimetype='image/jpeg') + + # アップロード実行 + file = service.files().create( + body=file_metadata, + media_body=media, + fields='id' + ).execute() + + return file.get('id') + + + # ✅ / にアクセスしたときの簡易応答(Render確認用) @app.route("/") def index(): From ef1629411c54abba656c4b29eb153bab9fffdf9e Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:23:12 +0900 Subject: [PATCH 08/22] Update app.py --- examples/flask-echo/app.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index aec00bc01..c30071c2f 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -111,12 +111,24 @@ def callback(): # ✅ 画像メッセージの場合は定型メッセージで応答 elif isinstance(event.message, ImageMessageContent): - line_bot_api.reply_message_with_http_info( - ReplyMessageRequest( - reply_token=event.reply_token, - messages=[TextMessage(text="画像を受け取りました!ありがとう📸")] - ) - ) + with ApiClient(configuration) as api_client: + line_bot_api = MessagingApi(api_client) + + # LINEの画像を取得 + content_response = line_bot_api.get_message_content(message_id=event.message.id) + image_bytes = b''.join(content_response.iter_content(chunk_size=1024)) + + # Google Drive にアップロード + file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") + + # ユーザーに返信 + line_bot_api.reply_message_with_http_info( + ReplyMessageRequest( + reply_token=event.reply_token, + messages=[TextMessage(text=f"画像を保存しました!(ID: {file_id})")] + ) + ) + return 'OK' From 125fe1c406f19739a61ccd907ffd02990089b8f8 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:37:40 +0900 Subject: [PATCH 09/22] Update app.py --- examples/flask-echo/app.py | 53 +++++++++++++------------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index c30071c2f..d9a17b6d2 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -7,13 +7,12 @@ from googleapiclient.discovery import build from googleapiclient.http import MediaIoBaseUpload - import os import sys from argparse import ArgumentParser from flask import Flask, request, abort -from linebot.v3.webhook import WebhookParser # ✅ 新SDKに対応 +from linebot.v3.webhook import WebhookParser from linebot.v3.exceptions import InvalidSignatureError from linebot.v3.webhooks import ( MessageEvent, @@ -30,7 +29,7 @@ app = Flask(__name__) -# チャネルシークレットとアクセストークンを環境変数から取得 +# 環境変数からLINEのチャネル情報を取得 channel_secret = os.getenv('LINE_CHANNEL_SECRET', None) channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None) @@ -41,31 +40,24 @@ print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.') sys.exit(1) -# WebhookパーサーとAPIクライアント設定 parser = WebhookParser(channel_secret) configuration = Configuration(access_token=channel_access_token) - +# ✅ Google Driveにアップロードする関数 def upload_to_drive(image_bytes, filename): - # 環境変数からBase64の認証情報を復元 credentials_json = base64.b64decode(os.environ['GOOGLE_CREDENTIALS_BASE64']).decode('utf-8') credentials_dict = json.loads(credentials_json) - - # 認証情報オブジェクトを作成 creds = service_account.Credentials.from_service_account_info(credentials_dict) - # Google Drive APIクライアントを初期化 service = build('drive', 'v3', credentials=creds) - # アップロードするファイルの設定 file_metadata = { 'name': filename, - 'parents': ['1PoDaKlXm788CXiHeTW9iEFGUwjlxVNBD'] # フォルダIDを指定 + 'parents': ['1PoDaKlXm788CXiHeTW9iEFGUwjlxVNBD'] # あなたのDriveフォルダID } media = MediaIoBaseUpload(io.BytesIO(image_bytes), mimetype='image/jpeg') - # アップロード実行 file = service.files().create( body=file_metadata, media_body=media, @@ -74,14 +66,12 @@ def upload_to_drive(image_bytes, filename): return file.get('id') - - -# ✅ / にアクセスしたときの簡易応答(Render確認用) +# ✅ 動作確認用ルート @app.route("/") def index(): return "LINE Bot is alive." -# ✅ Webhook受け取りエンドポイント +# ✅ Webhookエンドポイント @app.route("/callback", methods=['POST']) def callback(): signature = request.headers.get('X-Line-Signature', '') @@ -100,7 +90,7 @@ def callback(): with ApiClient(configuration) as api_client: line_bot_api = MessagingApi(api_client) - # ✅ テキストメッセージの場合はそのままオウム返し + # ✅ テキストメッセージ:オウム返し if isinstance(event.message, TextMessageContent): line_bot_api.reply_message_with_http_info( ReplyMessageRequest( @@ -109,30 +99,23 @@ def callback(): ) ) - # ✅ 画像メッセージの場合は定型メッセージで応答 + # ✅ 画像メッセージ:Google Driveに保存 elif isinstance(event.message, ImageMessageContent): - with ApiClient(configuration) as api_client: - line_bot_api = MessagingApi(api_client) - - # LINEの画像を取得 - content_response = line_bot_api.get_message_content(message_id=event.message.id) - image_bytes = b''.join(content_response.iter_content(chunk_size=1024)) + content_response = line_bot_api.get_message_content(message_id=event.message.id) + image_bytes = b''.join(content_response.iter_content(chunk_size=1024)) - # Google Drive にアップロード - file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") - - # ユーザーに返信 - line_bot_api.reply_message_with_http_info( - ReplyMessageRequest( - reply_token=event.reply_token, - messages=[TextMessage(text=f"画像を保存しました!(ID: {file_id})")] - ) - ) + file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") + line_bot_api.reply_message_with_http_info( + ReplyMessageRequest( + reply_token=event.reply_token, + messages=[TextMessage(text=f"画像を保存しました!(ID: {file_id})")] + ) + ) return 'OK' -# ✅ アプリ起動(Render対応) +# ✅ アプリ起動設定(Render対応) if __name__ == "__main__": arg_parser = ArgumentParser( usage='Usage: python ' + __file__ + ' [--debug] [--help]' From e537836fa8af9b8111039193c9bb79afcb150973 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:48:26 +0900 Subject: [PATCH 10/22] Update requirements.txt --- examples/flask-echo/requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/flask-echo/requirements.txt b/examples/flask-echo/requirements.txt index 9dd9f100b..30023101b 100644 --- a/examples/flask-echo/requirements.txt +++ b/examples/flask-echo/requirements.txt @@ -1,6 +1,6 @@ -line-bot-sdk -flask -google-api-python-client -google-auth -google-auth-httplib2 -google-auth-oauthlib +line-bot-sdk>=3.0.0 +flask>=2.0.0 +google-api-python-client>=2.0.0 +google-auth>=2.0.0 +google-auth-httplib2>=0.1.0 +google-auth-oauthlib>=1.0.0 From d9079ea4a62d7ff4c0d00ed4e85c20123b051d5c Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 15:55:29 +0900 Subject: [PATCH 11/22] Update requirements.txt From c230e23b12a001c346495c272639ad171273f0e3 Mon Sep 17 00:00:00 2001 From: conception24 Date: Sun, 6 Jul 2025 16:04:49 +0900 Subject: [PATCH 12/22] Update requirements.txt --- examples/flask-echo/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/flask-echo/requirements.txt b/examples/flask-echo/requirements.txt index 30023101b..db41cbe62 100644 --- a/examples/flask-echo/requirements.txt +++ b/examples/flask-echo/requirements.txt @@ -1,5 +1,6 @@ line-bot-sdk>=3.0.0 flask>=2.0.0 +google google-api-python-client>=2.0.0 google-auth>=2.0.0 google-auth-httplib2>=0.1.0 From 61458c928ede4a6992f290a171e1dc6980cb36de Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 00:08:26 +0900 Subject: [PATCH 13/22] Update requirements.txt --- examples/flask-echo/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flask-echo/requirements.txt b/examples/flask-echo/requirements.txt index db41cbe62..9d77b7560 100644 --- a/examples/flask-echo/requirements.txt +++ b/examples/flask-echo/requirements.txt @@ -1,5 +1,5 @@ -line-bot-sdk>=3.0.0 -flask>=2.0.0 +line-bot-sdk>=1.21.0 +flask>=2.3.3 google google-api-python-client>=2.0.0 google-auth>=2.0.0 From 30a0a0cd8649e00f8d897e81b31a105f38ef4693 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 00:10:20 +0900 Subject: [PATCH 14/22] Update app.py --- examples/flask-echo/app.py | 96 +++++++++++++++----------------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index d9a17b6d2..3afc1c550 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -3,45 +3,36 @@ import base64 import json import io -from google.oauth2 import service_account -from googleapiclient.discovery import build -from googleapiclient.http import MediaIoBaseUpload - import os import sys from argparse import ArgumentParser from flask import Flask, request, abort -from linebot.v3.webhook import WebhookParser -from linebot.v3.exceptions import InvalidSignatureError -from linebot.v3.webhooks import ( +from linebot import LineBotApi, WebhookHandler +from linebot.exceptions import InvalidSignatureError +from linebot.models import ( MessageEvent, - TextMessageContent, - ImageMessageContent -) -from linebot.v3.messaging import ( - Configuration, - ApiClient, - MessagingApi, - ReplyMessageRequest, - TextMessage + TextMessage, + ImageMessage, + TextSendMessage ) +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaIoBaseUpload + app = Flask(__name__) # 環境変数からLINEのチャネル情報を取得 -channel_secret = os.getenv('LINE_CHANNEL_SECRET', None) -channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None) +channel_secret = os.getenv('LINE_CHANNEL_SECRET') +channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN') -if channel_secret is None: - print('Specify LINE_CHANNEL_SECRET as environment variable.') - sys.exit(1) -if channel_access_token is None: - print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.') +if channel_secret is None or channel_access_token is None: + print("環境変数が設定されていません。") sys.exit(1) -parser = WebhookParser(channel_secret) -configuration = Configuration(access_token=channel_access_token) +line_bot_api = LineBotApi(channel_access_token) +handler = WebhookHandler(channel_secret) # ✅ Google Driveにアップロードする関数 def upload_to_drive(image_bytes, filename): @@ -74,52 +65,41 @@ def index(): # ✅ Webhookエンドポイント @app.route("/callback", methods=['POST']) def callback(): - signature = request.headers.get('X-Line-Signature', '') + signature = request.headers['X-Line-Signature'] body = request.get_data(as_text=True) - app.logger.info("Request body: " + body) try: - events = parser.parse(body, signature) + handler.handle(body, signature) except InvalidSignatureError: abort(400) - for event in events: - if not isinstance(event, MessageEvent): - continue - - with ApiClient(configuration) as api_client: - line_bot_api = MessagingApi(api_client) - - # ✅ テキストメッセージ:オウム返し - if isinstance(event.message, TextMessageContent): - line_bot_api.reply_message_with_http_info( - ReplyMessageRequest( - reply_token=event.reply_token, - messages=[TextMessage(text=event.message.text)] - ) - ) + return 'OK' - # ✅ 画像メッセージ:Google Driveに保存 - elif isinstance(event.message, ImageMessageContent): - content_response = line_bot_api.get_message_content(message_id=event.message.id) - image_bytes = b''.join(content_response.iter_content(chunk_size=1024)) +@handler.add(MessageEvent) +def handle_message(event): + if isinstance(event.message, TextMessage): + # テキストメッセージ:オウム返し + reply_text = event.message.text + line_bot_api.reply_message( + event.reply_token, + TextSendMessage(text=reply_text) + ) - file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") + elif isinstance(event.message, ImageMessage): + # 画像メッセージ:保存して返信 + message_content = line_bot_api.get_message_content(event.message.id) + image_bytes = b''.join(message_content.iter_content()) - line_bot_api.reply_message_with_http_info( - ReplyMessageRequest( - reply_token=event.reply_token, - messages=[TextMessage(text=f"画像を保存しました!(ID: {file_id})")] - ) - ) + file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") - return 'OK' + line_bot_api.reply_message( + event.reply_token, + TextSendMessage(text=f"画像を保存しました!(ID: {file_id})") + ) # ✅ アプリ起動設定(Render対応) if __name__ == "__main__": - arg_parser = ArgumentParser( - usage='Usage: python ' + __file__ + ' [--debug] [--help]' - ) + arg_parser = ArgumentParser() arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() From 2fd7205cab04695adfe2e89370b3ce463fde9c16 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 00:20:33 +0900 Subject: [PATCH 15/22] Update app.py --- examples/flask-echo/app.py | 42 +++----------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 3afc1c550..e48437ac4 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -import base64 -import json -import io import os import sys from argparse import ArgumentParser @@ -17,10 +14,6 @@ TextSendMessage ) -from google.oauth2 import service_account -from googleapiclient.discovery import build -from googleapiclient.http import MediaIoBaseUpload - app = Flask(__name__) # 環境変数からLINEのチャネル情報を取得 @@ -34,29 +27,6 @@ line_bot_api = LineBotApi(channel_access_token) handler = WebhookHandler(channel_secret) -# ✅ Google Driveにアップロードする関数 -def upload_to_drive(image_bytes, filename): - credentials_json = base64.b64decode(os.environ['GOOGLE_CREDENTIALS_BASE64']).decode('utf-8') - credentials_dict = json.loads(credentials_json) - creds = service_account.Credentials.from_service_account_info(credentials_dict) - - service = build('drive', 'v3', credentials=creds) - - file_metadata = { - 'name': filename, - 'parents': ['1PoDaKlXm788CXiHeTW9iEFGUwjlxVNBD'] # あなたのDriveフォルダID - } - - media = MediaIoBaseUpload(io.BytesIO(image_bytes), mimetype='image/jpeg') - - file = service.files().create( - body=file_metadata, - media_body=media, - fields='id' - ).execute() - - return file.get('id') - # ✅ 動作確認用ルート @app.route("/") def index(): @@ -79,22 +49,16 @@ def callback(): def handle_message(event): if isinstance(event.message, TextMessage): # テキストメッセージ:オウム返し - reply_text = event.message.text line_bot_api.reply_message( event.reply_token, - TextSendMessage(text=reply_text) + TextSendMessage(text=event.message.text) ) elif isinstance(event.message, ImageMessage): - # 画像メッセージ:保存して返信 - message_content = line_bot_api.get_message_content(event.message.id) - image_bytes = b''.join(message_content.iter_content()) - - file_id = upload_to_drive(image_bytes, f"{event.message.id}.jpg") - + # 画像メッセージ:「画像ありがとう!」と返信(保存はしない) line_bot_api.reply_message( event.reply_token, - TextSendMessage(text=f"画像を保存しました!(ID: {file_id})") + TextSendMessage(text="画像ありがとう!") ) # ✅ アプリ起動設定(Render対応) From 98eb61e4f9d16810a0a3dfa1023547bacb666b0f Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 01:12:25 +0900 Subject: [PATCH 16/22] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E5=87=BA=E3=81=99=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F=20app.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/flask-echo/app.py | 80 ++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index e48437ac4..0443d4c45 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- - import os import sys +import logging from argparse import ArgumentParser - from flask import Flask, request, abort from linebot import LineBotApi, WebhookHandler from linebot.exceptions import InvalidSignatureError @@ -14,13 +13,26 @@ TextSendMessage ) +# ✅ ログ設定を追加 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + app = Flask(__name__) # 環境変数からLINEのチャネル情報を取得 channel_secret = os.getenv('LINE_CHANNEL_SECRET') channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN') +# ✅ 環境変数の確認ログを追加 +logger.info("=== 環境変数チェック ===") +logger.info(f"Channel Secret: {'設定済み' if channel_secret else '未設定'}") +logger.info(f"Access Token: {'設定済み' if channel_access_token else '未設定'}") + if channel_secret is None or channel_access_token is None: + logger.error("環境変数が設定されていません。") print("環境変数が設定されていません。") sys.exit(1) @@ -30,42 +42,76 @@ # ✅ 動作確認用ルート @app.route("/") def index(): + logger.info("/ エンドポイントにアクセスされました") return "LINE Bot is alive." # ✅ Webhookエンドポイント @app.route("/callback", methods=['POST']) def callback(): - signature = request.headers['X-Line-Signature'] + logger.info("=== Webhook受信 ===") + + signature = request.headers.get('X-Line-Signature') body = request.get_data(as_text=True) - + + logger.info(f"署名: {signature[:10]}..." if signature else "署名なし") + logger.info(f"ボディ長: {len(body)} 文字") + try: handler.handle(body, signature) + logger.info("メッセージ処理完了") except InvalidSignatureError: + logger.error("署名検証エラー - Channel Secretが間違っている可能性があります") abort(400) - + except Exception as e: + logger.error(f"予期しないエラー: {str(e)}") + abort(500) + return 'OK' @handler.add(MessageEvent) def handle_message(event): + logger.info("=== メッセージ受信 ===") + logger.info(f"イベントタイプ: {type(event.message)}") + if isinstance(event.message, TextMessage): - # テキストメッセージ:オウム返し - line_bot_api.reply_message( - event.reply_token, - TextSendMessage(text=event.message.text) - ) - + logger.info(f"テキストメッセージ: {event.message.text}") + + try: + # テキストメッセージ:オウム返し + line_bot_api.reply_message( + event.reply_token, + TextSendMessage(text=event.message.text) + ) + logger.info("テキストメッセージ返信完了") + + except Exception as e: + logger.error(f"テキストメッセージ返信エラー: {str(e)}") + elif isinstance(event.message, ImageMessage): - # 画像メッセージ:「画像ありがとう!」と返信(保存はしない) - line_bot_api.reply_message( - event.reply_token, - TextSendMessage(text="画像ありがとう!") - ) + logger.info("画像メッセージを受信") + + try: + # 画像メッセージ:「画像ありがとう!」と返信(保存はしない) + line_bot_api.reply_message( + event.reply_token, + TextSendMessage(text="画像ありがとう!") + ) + logger.info("画像メッセージ返信完了") + + except Exception as e: + logger.error(f"画像メッセージ返信エラー: {str(e)}") + else: + logger.info(f"未対応のメッセージタイプ: {type(event.message)}") # ✅ アプリ起動設定(Render対応) if __name__ == "__main__": arg_parser = ArgumentParser() arg_parser.add_argument('-d', '--debug', default=False, help='debug') options = arg_parser.parse_args() - port = int(os.environ.get("PORT", 10000)) + + logger.info(f"=== アプリ起動 ===") + logger.info(f"ポート: {port}") + logger.info(f"デバッグモード: {options.debug}") + app.run(host="0.0.0.0", debug=options.debug, port=port) From 975682cc3a8f6fb70de75607a262efcd4b97eea9 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 16:00:05 +0900 Subject: [PATCH 17/22] =?UTF-8?q?BytesIO=E3=81=AB=E6=A0=BC=E7=B4=8D.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/flask-echo/app.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 0443d4c45..ea20d20e8 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -88,18 +88,31 @@ def handle_message(event): logger.error(f"テキストメッセージ返信エラー: {str(e)}") elif isinstance(event.message, ImageMessage): - logger.info("画像メッセージを受信") - + logger.info("画像メッセージを受信") + try: - # 画像メッセージ:「画像ありがとう!」と返信(保存はしない) + # 画像データを取得してBytesIOに格納 + from io import BytesIO + message_id = event.message.id + message_content = line_bot_api.get_message_content(message_id) + + image_data = BytesIO() + for chunk in message_content.iter_content(): + image_data.write(chunk) + image_data.seek(0) + + logger.info("画像データをBytesIOに保存完了") + + # 仮返信 line_bot_api.reply_message( event.reply_token, - TextSendMessage(text="画像ありがとう!") + TextSendMessage(text="画像を受け取りました!") ) logger.info("画像メッセージ返信完了") - - except Exception as e: - logger.error(f"画像メッセージ返信エラー: {str(e)}") + + except Exception as e: + logger.error(f"画像処理エラー: {str(e)}") + else: logger.info(f"未対応のメッセージタイプ: {type(event.message)}") From faa74d68b1f2c942622085281d16cb8c07262c69 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 16:05:25 +0900 Subject: [PATCH 18/22] =?UTF-8?q?BytesIO=E3=81=AB=E6=A0=BC=E7=B4=8D?= =?UTF-8?q?=EF=BC=92.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/flask-echo/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index ea20d20e8..ca059c523 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -88,7 +88,7 @@ def handle_message(event): logger.error(f"テキストメッセージ返信エラー: {str(e)}") elif isinstance(event.message, ImageMessage): - logger.info("画像メッセージを受信") + logger.info("画像メッセージを受信") try: # 画像データを取得してBytesIOに格納 From b8890562fa0ca75e462b082ce3b1403463d01810 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 16:15:12 +0900 Subject: [PATCH 19/22] BytesIO3.py --- examples/flask-echo/app.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index ca059c523..337ed88ca 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -68,6 +68,7 @@ def callback(): return 'OK' +# ✅ ユーザーからのメッセージ処理(Text / Image) @handler.add(MessageEvent) def handle_message(event): logger.info("=== メッセージ受信 ===") @@ -86,10 +87,10 @@ def handle_message(event): except Exception as e: logger.error(f"テキストメッセージ返信エラー: {str(e)}") - + elif isinstance(event.message, ImageMessage): logger.info("画像メッセージを受信") - + try: # 画像データを取得してBytesIOに格納 from io import BytesIO @@ -100,22 +101,23 @@ def handle_message(event): for chunk in message_content.iter_content(): image_data.write(chunk) image_data.seek(0) - + logger.info("画像データをBytesIOに保存完了") - + # 仮返信 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="画像を受け取りました!") ) logger.info("画像メッセージ返信完了") - - except Exception as e: - logger.error(f"画像処理エラー: {str(e)}") + except Exception as e: + logger.error(f"画像処理エラー: {str(e)}") + else: logger.info(f"未対応のメッセージタイプ: {type(event.message)}") + # ✅ アプリ起動設定(Render対応) if __name__ == "__main__": arg_parser = ArgumentParser() From 433225ac43e36eabe792b64de1b387d1d8350c21 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 17:10:51 +0900 Subject: [PATCH 20/22] =?UTF-8?q?google=E5=BF=85=E8=A6=81=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA3=E3=81=A4=E5=B0=8E?= =?UTF-8?q?=E5=85=A5=E3=81=99=E3=82=8B.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/flask-echo/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 337ed88ca..3ce7f1842 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -12,6 +12,9 @@ ImageMessage, TextSendMessage ) +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaIoBaseUpload # ✅ ログ設定を追加 logging.basicConfig( From 60211e75c65cc9454e710ea33a4ac129074d1279 Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 17:21:54 +0900 Subject: [PATCH 21/22] =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=82=92=E3=82=B0?= =?UTF-8?q?=E3=83=BC=E3=82=B0=E3=83=AB=E3=83=89=E3=83=A9=E3=82=A4=E3=83=96?= =?UTF-8?q?=E3=81=AB=E4=BF=9D=E5=AD=98.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/flask-echo/app.py | 52 +++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/examples/flask-echo/app.py b/examples/flask-echo/app.py index 3ce7f1842..8f9d6c6ff 100644 --- a/examples/flask-echo/app.py +++ b/examples/flask-echo/app.py @@ -71,6 +71,42 @@ def callback(): return 'OK' +# ✅グーグルドライブにアップロードする関数を定義する +def upload_image_to_drive(image_data, filename, folder_id): + """Google Driveに画像をアップロードする""" + try: + # サービスアカウント認証 + creds = service_account.Credentials.from_service_account_file( + 'html-quiz-app-465012-4d1ecaf6b3c8.json', + scopes=['https://www.googleapis.com/auth/drive'] + ) + + # Drive APIクライアント作成 + service = build('drive', 'v3', credentials=creds) + + # メタデータ定義 + file_metadata = { + 'name': filename, + 'parents': [folder_id] + } + + # アップロード用メディアオブジェクト + media = MediaIoBaseUpload(image_data, mimetype='image/png') + + # アップロード実行 + file = service.files().create( + body=file_metadata, + media_body=media, + fields='id' + ).execute() + + logger.info(f"Google Driveにアップロード成功!ファイルID: {file.get('id')}") + + except Exception as e: + logger.error(f"Google Driveアップロード中にエラー: {str(e)}") + + + # ✅ ユーザーからのメッセージ処理(Text / Image) @handler.add(MessageEvent) def handle_message(event): @@ -97,6 +133,8 @@ def handle_message(event): try: # 画像データを取得してBytesIOに格納 from io import BytesIO + from datetime import datetime # ← 追加(上に書いてあれば不要) + message_id = event.message.id message_content = line_bot_api.get_message_content(message_id) @@ -107,7 +145,18 @@ def handle_message(event): logger.info("画像データをBytesIOに保存完了") - # 仮返信 + # ミリ秒単位のファイル名を生成 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + filename = f"{timestamp}.png" + + # Google Driveへアップロード + upload_image_to_drive( + image_data=image_data, + filename=filename, + folder_id="1PoDaKlXm788CXiHeTW9iEFGUwjlxVNBD" + ) + + # LINE返信 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="画像を受け取りました!") @@ -121,6 +170,7 @@ def handle_message(event): logger.info(f"未対応のメッセージタイプ: {type(event.message)}") + # ✅ アプリ起動設定(Render対応) if __name__ == "__main__": arg_parser = ArgumentParser() From 106dade96e86afc3f8eceb2e6b531988b5d0275b Mon Sep 17 00:00:00 2001 From: conception24 Date: Mon, 7 Jul 2025 17:36:27 +0900 Subject: [PATCH 22/22] Add files via upload --- .../html-quiz-app-465012-4d1ecaf6b3c8.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 examples/flask-echo/html-quiz-app-465012-4d1ecaf6b3c8.json diff --git a/examples/flask-echo/html-quiz-app-465012-4d1ecaf6b3c8.json b/examples/flask-echo/html-quiz-app-465012-4d1ecaf6b3c8.json new file mode 100644 index 000000000..c2bda34f7 --- /dev/null +++ b/examples/flask-echo/html-quiz-app-465012-4d1ecaf6b3c8.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "html-quiz-app-465012", + "private_key_id": "4d1ecaf6b3c80e1d62e4de48f119e4bf122db2ad", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCdS4qhFPCwyL2P\n97KwHna1NLiP5q1XG1oNuEdMW4ZpLh9qNHcz4s20cy3zbFE2rhYBIymDoCE43xfL\nkBcMekopWcN7oL+Z07yGoJGzMnkPrpQYQkN5eNB/GfVMdUhRzvtK7lTJSQgKU5zw\n41OD6JxypuDWPuJeuu858OE92oZ6sBltL8+Jv5/YfJBn6iioIcMXVMM2Bi+owSlt\nax+P7gUpAgx9QEnrtct46CiDhH0LV38O64ff9aQo1ob9w92Cc93OPBM12TXL9IRR\nzYtuQkSyQQsCJo1/0ZunPx4D+I542P+dMOXfdtJ9T26x8GsrILKgVYJXNFgNE2og\nTx4sF2TrAgMBAAECggEABC0ihcohx0dfDsBFDf84mPFQSh2V/1T1lLuOeYkur+SW\nIy3OusdMLowPolSk6bne2Tm0qGleEOroLKej25zlQAL9tYzlmmAm/t26EY1shGIi\nL6iWCTi9M7o/torc/e+KLOBBjoQDHysRvyU9x4nRP+2UmKjUiT4Pmyl1tjynocR7\n6TdfWb12IUakscTqmXewvT2MQZf4wng/lSSM6mbzuqUVl9pWV7WV1Ov8eiHFijFl\nFaa8YjuXv/qDiK05MSLT23dzQKGpsfroLhQgLbPTYNjMYqh4RErxHutf4hcq++f1\nThVTwGaicsE94iQUkPxP3wQ+Z92LYgRAfH2m36fpUQKBgQDbfZJddxH1t3m5TTxN\nbRnyVXlhP3ESf6n/wgQGdG5z/9vWyFUxhfOTMyw9u6QaQZwaLzPkGni84+PywHka\nFtwdkiWINgzhuyw/bdnTxDWAuYt7FH8c2SUYrA1myWo9DtVD4e6ZBjDp5dzpbW0I\nBRJKYmvRN3G/BKSOqw4jUC9a0wKBgQC3dYpXG3JgXVIrRl7x7Q6JCpi5YmxwaNp9\nNiutqe5s5foQdaheXCDlJLXepcJ5LEl7L7D4iQqwEQ+6mfiTj8O045jLolPxkGLw\nfywPzoAxKqBronqfUJpjtUGFef4M1gKe2KaeOI9EN4j/MTyscKf2Zj3pvblYMxvV\nLaVbu27OiQKBgQCq/R08ANmV8ZQ7PicfLkJLXjkMAG9m+NuHqi6WlLfDWGx72187\nEF5bqz7o7Lf/2GLypxkmt1Sd2m8nGrh9EZfy1Xq1rV3FsJnc5AgRCEah/GnjvWj1\nIVjb6pUaIQjJfje4BVdqEL+kR3UcyClrWQARwLC7sRTEuao0lp+R9RKfDwKBgQCR\ns68wKSM4d0U4C0aCmwPtQNY0fX+j8xJ7WRdtcEVkhW2Jvvf+IOsbMYGjl0ARXmPp\niuHhMW2kkZhNWdV61DXZJt0F9SD3+/UoDcgfsaBL7A/GDB+ZAbnsMFseN9nVte4U\n2/nQNRlLU1PhYOhQB5TN8zt+tOhLrSOQthTa+swxCQKBgQCFY99sQjEXP9lrgZly\nlF9YLMXscuOyLnRcjgKzDstZqGqI1T0uuH9sgutXBeU1XAGlIwoNSHCHCEG06fRc\n9QIj4dHv1IKkPVPPPH62VkDg8rtXIxx4bdUebJKEDJLTY0w2R0CZvnixDf/d1Dty\nR6+fLRJbdYq9+V4EU6i+3tErHg==\n-----END PRIVATE KEY-----\n", + "client_email": "line-933@html-quiz-app-465012.iam.gserviceaccount.com", + "client_id": "109339131093495886447", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/line-933%40html-quiz-app-465012.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +}