盆栽の管理向上をめざして 6:LINE Botの作り込み
はじめに
前回2回にわたり、LINE BotとGoogle Apps Scriptを使う方法を模索していました。基本のオウム返しBotができたので、次のステップへ進んでいくことにいたします。
Google Apps Scriptを用いてやりたいこと
だいぶんやりたいことが見えてきました。
このような感じです。
LINEで画像とコメントを盆栽画像投稿Botへつぶやくと、Apps Scriptでそのメッセージを受け取り、「画像」フォルダへ画像を格納し、同時にメッセージ情報(key、コメント)をスプレッドシートに記入するものです。
これができれば、画像とkeyの紐づけができるため、今後のデータベース化へ向けて現実味が増します。
前回までのサンプルでLINE Messaging APIとGoogle Apps Script間でのメッセージのやり取りは確認できています。今回はその先の処理を考えていきたいと思います。
どうするか?
ものづくりをする全ての人にとって、この瞬間がたまらなく幸せな時間であると思っています。
私は情報収集をある程度行った後、「行けるかも」と思ったあたりで動き始めます。
今回もその情報収集から始めたいと思っています。
手当たり次第に行うと収拾がつかなくなるので、最初に目標を決めておきます。
・LINE Messaging APIに関する情報
・Google Apps Scriptに関する情報
・LINEとGASを用いてGoogleドライブに画像を格納する実例があるか
・LINEとGASを用いてGoogleスプレッドシートに情報を記録する実例があるか
調査を開始します。
LINE Messaging APIに関する情報
Messaging APIリファレンス
https://developers.line.biz/ja/reference/messaging-api/
まずは理解できなくても一通り目を通し、分量の確認、おおよその感じをつかみます。一番知りたいのは画像とテキストメッセージがどのようなフォーマットで送られるのかということです。
メッセージイベント
https://developers.line.biz/ja/reference/messaging-api/#message-event
ここに文字と画像の情報がありました。今の段階ではあまり理解できていませんが、実際のコードを作る段階でここのお世話になることでしょう。
Google Apps Scriptに関する情報
Apps Script
https://developers.google.com/apps-script?hl=ja
ここから始めます。
Google Apps Scriptで気を付けなければならないのは、似たようなサービスがたくさんあることです。前々回の失敗、Google Apps ScriptとGoogle Cloud Platformの混同を繰り返さないよう注意します。
Google Apps Script の概要
https://developers.google.com/apps-script/overview?hl=ja
結構な分量で多岐にわたる資料が見つかりました。
私の苦手なJavaScriptで記述することになるので、そのあたりの勉強も必要かもしれません。
公式サイトでは、Googleが提供したものではないことを断りながら下記のサイトを紹介していました。
codecademy/JavaScript
https://www.codecademy.com/catalog/language/javascript
全て英語です。が、昨年、Androidアプリを作成する際に、ブラウザを右クリックして「日本語へ翻訳」機能を覚えたので楽勝です。
必要になったらここも勉強します。
Google Cloud プロジェクト
https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=ja
注意すべき情報を見つけました。
Google Apps ScriptやGoogle Cloud Platformに似た用語「Apps Script Cloud プロジェクト」が出てきました。
今はそっとしておきます。
正直、とても難しいです。一本道ではなく、3本ぐらい道が通っていて、それらを必要に応じて使い分けるような感じがしています。今はあまり深入りしません。
LINEとGASを用いてGoogleドライブに画像を格納する実例があるか
Google検索で「LINEとGASを用いてGoogleドライブに画像を格納する」で探すと、それらしい記事がいくつか見つかります。
安心しました。
というより、ここまでの紆余曲折で検索キーワードの解像度が上がってるのだと思います。
LINEとGASを用いてGoogleスプレッドシートに情報を記録する実例があるか
こちらも大丈夫そうです。
ここまで、アスファルト舗装された道を軽快に飛ばせています。
次は実際の作成に取り掛かります
Googleドライブの盆栽管理フォルダ内にある「盆栽画像投稿」を開きます。
前回のオウム返しBotと同じスクリプトが入っています。
このスクリプトをサンプルをコピペしながら、やってみます。
まずは難しそうな画像の取り込みから
検索順位一位の下記のサイトを参考にしました。
Yuki's BNB blogさんの
【シリーズ第2話】Google Driveに画像を自動保存するLINE BotをGASで作ろう (実践編)
準備として、LINEからの画像を受け付けるフォルダを作成し、フォルダIDを取得します。
Moment.jsというライブラリを使えるようにしました。
とにかく最初は写経します。
手順に沿って進めました。
もうすでに、LINE Botとしては成立しているので、スクリプトを差し替えてデプロイします。
デプロイすると、新しいurlになるので、それをLINEに登録して完成します。
ちょっとだけ気になっていたことがあって、調べてみます。
ファイルサイズです。転送前の画像はJPG2048x2048でサイズは329 KBでした。転送後の画像はPNG2048x2048でサイズは5.7 MBです。
これは何とかできないものか。。。
全くの素人考えで、
を
に書き換えてみました。
きちんと勉強しなければなりませんが、この一行で、JPEGでの保存となり、ファイルサイズは増加しませんでした。
素晴らしいです!!あっという間に一つできてしまいました。
今度は、スプレッドシートにkeyを取りこむようにします。
大きな勘違いをしていたようです。
私はLINEで画像とテキストを同時に送れると思っていました。
大きな誤りで同時には送れませんでした。
画像を登録し、続いてテキストを登録する
という方法でよいのではないでしょうか?
ただ、このあたりから自分でソースをいじり始めますので、
デバッグ出力がほしくなります。
どうもこのままでは先に進めそうもないので、
@Ko001さん
GAS × Line Messaging Api のデバッグ
https://qiita.com/Ko001/items/f72f7dc59116d961581e
こちらを参考に進めて行きます。
なんとなくわかってきました。
おおよそのフローチャートを考えます。
走り切りました。
ここまで見えれば、後はともかく走るのみです。
prtn-blogさん
LINE Messaging API×GASで家計簿管理用のLINE botを作成する:スプレッドシートへの書き込み
https://prtn-life.com/blog/gas-linebot-regist-spreadsheet
こちらの情報も大変役立ちました。
そしていろんなサイトを参考に走り切った結果がこちらです。
LINE Botで画像を投稿すると、key、コメントを入力するように促されます。
そのまま、key、コメントを入力すると、先ほどの画像に紐づけて、key、コメントと追記します。
ファイル名、日付、url、key、コメント のリストができました。
JavaScriptに関する情報はとても多く、さらに、GASに関する情報も溢れています。
いろんな情報をつまみ食いするだけで、どんどん機能が実装できてしまいます。
こんな感じでとてもお恥ずかしいプログラムになったのですが、今後の参考のため貼っておきます。
自分の好みの書き方で整理したいです。
var LINE_ACCESS_TOKEN = "LINE Messaging APIのチャネルアクセストークン";
var GOOGLE_DRIVE_FOLDER_ID = "画像を保存するフォルダーID";
//LINE Messaging APIからPOST送信を受けたときに起動する
// e はJSON文字列
function doPost(e){
if (typeof e === "undefined"){
//eがundefinedの場合動作を終了する
return;
}
//JSON文字列をパース(解析)し、変数jsonに格納する
var json = JSON.parse(e.postData.contents);
//受信したメッセージ情報を変数に格納する
// LINE側へ応答するためのトークンを作成(LINEからのリクエストに入っているので、それを取得する)
var reply_token = json.events[0].replyToken; //reply token
if (typeof reply_token === 'undefined') {
return;
}
var messageId = json.events[0].message.id; //メッセージID
var messageType = json.events[0].message.type; //メッセージタイプ
switch (messageType) {
case 'image':
debug("doPost:image");
var LINE_END_POINT = "https://api-data.line.me/v2/bot/message/" + messageId + "/content";
//変数LINE_END_POINTとreply_tokenを関数getImageに渡し、getImageを起動する
getImage(LINE_END_POINT, reply_token);
break;
case 'text':
debug("doPost:text");
var userMessage = json.events[0].message.text;
// 改行で区切って配列にする
var userMessageArray = userMessage.split(/\r\n|\n/);
//もう少し整理する
var imageFileKey = userMessageArray[0];
if (userMessageArray.length >= 2) {
var imageFileComment = userMessageArray[1];
}
else {
var imageFileComment = "";
}
registImageFileInfo(imageFileKey, imageFileComment);
var message = "key:" + imageFileKey + "\nコメント:" + imageFileComment + "\n-----\n追記しました";
sendMessage(reply_token, message);
break;
default:
var messageNotImage = "画像を送信してください";
sendMessage(reply_token, messageNotImage);
break;
}
}
//Blob形式で画像を取得する
function getImage(LINE_END_POINT, reply_token){
//ファイル名に使う現在日時をMomentライブラリーを使って取得
var date = Moment.moment(); //現在日時を取得
var formattedDate = date.format("YYYYMMDD_HHmmss");
try {
var url = LINE_END_POINT;
var headers = {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + LINE_ACCESS_TOKEN
};
var options = {
"method" : "get",
"headers" : headers,
};
var res = UrlFetchApp.fetch(url, options);
//Blob形式で画像を取得し、ファイル名を設定する
//ファイル名: LINE画像_YYYYMMDD_HHmmss.png
//var imageBlob = res.getBlob().getAs("image/png").setName("LINE画像_" + formattedDate + ".png")
var imageBlob = res.getBlob().getAs("image/jpeg").setName("LINE画像_" + formattedDate + ".jpg");
//変数imageBlobとreply_tokenを関数saveImageに渡し、saveImageを起動する
saveImage(imageBlob, reply_token)
} catch(e) {
//例外エラーが起きた時にログを残す
Logger.log(e.message);
debug(e.message);
}
}
//画像をGoogle Driveのフォルダーに保存する
function saveImage(imageBlob, reply_token){
try{
var folder = DriveApp.getFolderById(GOOGLE_DRIVE_FOLDER_ID);
var file = folder.createFile(imageBlob);
var imageFileName = file.getName();
var imageFileUrl = file.getUrl();
registImageFile(imageFileName, imageFileUrl);
// var message = "「" + folder.getName() + "」に画像を保存しました";
var message = imageFileName + ":\n-----\nkey\nコメント\n-----\nを入力してください。";
//変数reply_tokenとmessageを関数sendMessageに渡し、sendMessageを起動する
sendMessage(reply_token, message)
} catch(e){
//例外エラーが起きた時にログを残す
Logger.log(e)
debug(e);
}
}
//ユーザーにメッセージを送信する
function sendMessage(reply_token, text){
//返信先URL
var replyUrl = "https://api.line.me/v2/bot/message/reply";
var headers = {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + LINE_ACCESS_TOKEN
};
var postData = {
"replyToken": reply_token,
"messages": [{
"type": "text",
"text": text
}]
};
var options = {
"method" : "post",
"headers" : headers,
"payload" : JSON.stringify(postData)
};
//LINE Messaging APIにデータを送信する
UrlFetchApp.fetch(replyUrl, options);
}
function registImageFile(imageFileName, imageFileUrl) {
var IMAGE_FILE_SHEET_ID = '画像管理シートのID';
const sheet = SpreadsheetApp.openById(IMAGE_FILE_SHEET_ID);
const ss = sheet.getSheetByName('LINEより');
const date = new Date();
const targetRow = ss.getLastRow() + 1;
ss.getRange('A' + targetRow).setValue(imageFileName);
ss.getRange('B' + targetRow).setValue(date);
ss.getRange('C' + targetRow).setValue(imageFileUrl);
}
function registImageFileInfo(imageFileKey, imageFileComment) {
var IMAGE_FILE_SHEET_ID = '画像管理シートのID';
const sheet = SpreadsheetApp.openById(IMAGE_FILE_SHEET_ID);
const ss = sheet.getSheetByName('LINEより');
const targetRow = ss.getLastRow(); // 注意:最終行のkey,コメントを記入する。
ss.getRange('D' + targetRow).setValue(imageFileKey);
ss.getRange('E' + targetRow).setValue(imageFileComment);
}
//doPost(e)の外に書く
function debug(value='デバッグテスト') {
var DEBUG_LOG_SHEET_ID = 'デバッグ用シートのID';
const sheet = SpreadsheetApp.openById(DEBUG_LOG_SHEET_ID);
const ss = sheet.getSheetByName('logs');
const date = new Date();
const targetRow = ss.getLastRow() + 1;
ss.getRange('A' + targetRow).setValue(date);
ss.getRange('B' + targetRow).setValue(value);
}
ふりかえり
後は実際に使って見て少しずつ改良していきたいと思います。
では、最初に振り返りChatGPT先生から教えていただいた内容を見てみましょう。
You
ChatGPT
from google.oauth2 import service_account
from googleapiclient.discovery import build
from linebot import LineBotApi
# LINE BotのトークンとChannel Secret
INE_CHANNEL_ACCESS_TOKEN = 'Your_Line_Channel_Access_Token'
ine_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)
# Googleサービスアカウントの認証情報
COPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets']
SERVICE_ACCOUNT_FILE = 'path/to/your/service-account-file.json'
edentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
# GoogleドライブとスプレッドシートのAPIクライアント
rive_service = build('drive', 'v3', credentials=credentials)
sheets_service = build('sheets', 'v4', credentials=credentials)
# LINEからのメッセージと画像の処理
@handler.add(MessageEvent, message=ImageMessage)
def handle_image_message(event):
message_id = event.message.id
message_content = line_bot_api.get_message_content(message_id)
# 画像をGoogleドライブにアップロード
file_metadata = {'name': 'image.jpg', 'parents': ['your_folder_id']}
media = MediaIoBaseUpload(io.BytesIO(message_content.content), mimetype='image/jpeg')
file = drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute()
file_id = file.get('id')
# Googleスプレッドシートにデータを書き込む
spreadsheet_id = 'your_spreadsheet_id'
values = [['=HYPERLINK("https://drive.google.com/uc?id=' + file_id + '")', 'Your Message']]
body = {'values': values}
result = sheets_service.spreadsheets().values().append(spreadsheetId=spreadsheet_id, range='Sheet1', body=body, valueInputOption='RAW').execute()
こんな感じでした。
ちょっと前の私はこの答えを見て、「できる」を確信しましたが、今振り返ってみると、こんなでは何もわからない状態だと言えます。
というより、ひょっとしたらこの方法を貫いていたらできたかもしれませんが、GCPではなく、GASを使うように変更したので、後半は自力になったのだと思います。
ともかく、たいへん短い間に作り上げることができました。
さいごに
何とか結果を出すことができました。
これでずいぶん日々の画像記録が楽になったと思います。
最後までお読みいただきまして誠にありがとうございました。
#盆栽管理 #画像管理 #紐づけ #LINEBot #GAS #Googleドライブ #Googleスプレッドシート