見出し画像

盆栽の管理向上をめざして 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です。

これは何とかできないものか。。。
全くの素人考えで、

var imageBlob = res.getBlob().getAs("image/png").setName("LINE画像_" + formattedDate + ".png")

var imageBlob = res.getBlob().getAs("image/jpeg").setName("LINE画像_" + formattedDate + ".jpg")

に書き換えてみました。

きちんと勉強しなければなりませんが、この一行で、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

LINE botを使って、Googleドライブに画像を転送し、Googleスプレッドシートにリンクとメッセージを記入する。

ChatGPT

LINE Botを使用して、Googleドライブに画像を転送し、Googleスプレッドシートにリンクとメッセージを記入するためには、以下の手順を実行する必要があります。以下は、一般的な手順の概要です。

1.LINE Developer ConsoleでMessaging APIを設定する:
 LINE Botを作成し、Messaging APIを有効にします。必要なトークンを取得します。
2.Google Cloud Platform (GCP)でプロジェクトを作成する:
 GoogleドライブとGoogle Sheets APIを有効にします。
 APIキーまたは認証情報を取得します。
3.LINE Botサーバーをセットアップする:
 LINE Botサーバーをセットアップし、LINE Messaging APIを使用してLINEとの連携を確立します。
4.Googleドライブに画像をアップロードする:
 LINE Botが画像を受信したとき、その画像をGoogleドライブにアップロードします。これにはGoogleドライブ APIを使用します。
5.Googleスプレッドシートにリンクとメッセージを記入する:
 Googleドライブにアップロードされた画像のリンクとLINEからのメッセージを取得し、それをGoogleスプレッドシートに記入します。Google Sheets APIを使用します。

以下は、Pythonを使用してGoogleドライブとGoogleスプレッドシートにアクセスするための簡単なサンプルコードの例です。実際の使用には、適切な認証とエラーハンドリングを含める必要があります。

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

上記のコードは一般的な流れを示しており、実際の要件に合わせて調整する必要があります。各APIの詳細な使い方については、Google Cloud PlatformとLINE Messaging APIのドキュメントを参照してください。

こんな感じでした。

ちょっと前の私はこの答えを見て、「できる」を確信しましたが、今振り返ってみると、こんなでは何もわからない状態だと言えます。

というより、ひょっとしたらこの方法を貫いていたらできたかもしれませんが、GCPではなく、GASを使うように変更したので、後半は自力になったのだと思います。

ともかく、たいへん短い間に作り上げることができました。

さいごに

何とか結果を出すことができました。
これでずいぶん日々の画像記録が楽になったと思います。

最後までお読みいただきまして誠にありがとうございました。

#盆栽管理 #画像管理 #紐づけ #LINEBot #GAS #Googleドライブ #Googleスプレッドシート


この記事が気に入ったらサポートをしてみませんか?