届いた LINE メッセージをスプレッドシートに書き込む2

前回の続きです。
前回はテキストのみ(JSON オブジェクト毎)だったので、どうせなら画像も保存できるようにします。

前回の流用

開発環境は前回を参照してください。
LINE 公式アカウントを作成して、GAS とスプレッドシートが用意できていれば大丈夫です。

テキストのみを書き込むようにする

まずは、テキストのみを書き込むようにします。
ここを見てみると、メッセージにはタイプがあるらしいので、タイプ毎に処理します。

function doPost(e) {
  // JSON はパーズしておく
  const postData = JSON.parse(e.postData.contents);
  // メッセージタイプがテキストの場合
  writeTextMessage(postData);
}

let chatBotSpreadsheet = null;
// スプレッドシートを取得する
function getSpreadsheet() {
  // スプレッドシートの ID
  const SSID = 'スプレッドシートの ID;
  if(chatBotSpreadsheet === null) chatBotSpreadsheet = SpreadsheetApp.openById(SSID);
  return chatBotSpreadsheet;
}

// テキストを最終行に追記する
// @param [string, string...] texts
function writeToLastRange(texts) {
  const sheet = getSpreadsheet().getSheets()[0];
  sheet.appendRow(texts);
}

// 送られてきたテキストメッセージをスプレッドシートに書き込む
function writeTextMessage(mesObj) {
  for(let i = 0; i < mesObj.events.length; i ++) {
    writeToLastRange([mesObj.events[i].message.text]);
  }
}

とりあえず受信したテキストメッセージをスプレッドシートに書き込めるようにしました。
試してみるとこんな感じになります。

自分のアカウントから開発用の公式 LINE に送信する

書き込まれていることを確認

受信した画像を保存してみる

受信したテキストメッセージの書き込みができるようになったので、次は受信した画像をマイドライブ内に保存してみます。

画像保存用のフォルダーを作成する

フォルダーの ID を控えておく

テキストメッセージは、post されたデータからテキストを取り出せば良いのですが、
画像の場合は、post されたデータの id を利用し、LINE のエンドポイントから取得します。
詳細はこちら
とても簡単に説明すると、
画像のメッセージを受信→受信したメッセージの id からエンドポイントにリクエストを送る→
返ってきたレスポンスから Blob を作成→ Blob からイメージファイルを作成する
という流れです。
Blob オブジェクトは何かのファイルを作成する元みたいなものです。
Blob オブジェクトについて。
なので、Blob オブジェクトから PDF や スプレッドシートなども作成できます。

function doPost(e) {
  // JSON はパーズしておく
  const postData = JSON.parse(e.postData.contents);
  for(let i = 0; i < postData.events.length; i++) {
    postReception(postData.events[i]);
  }
}

let chatBotSpreadsheet = null;
// スプレッドシートを取得する
function getSpreadsheet() {
  // スプレッドシートの ID
  const SSID = PropertiesService.getScriptProperties().getProperty('SPREAD_SHEET_ID');
  if(chatBotSpreadsheet === null) chatBotSpreadsheet = SpreadsheetApp.openById(SSID);
  return chatBotSpreadsheet;
}

// テキストを最終行に追記する
// @param [string, string...] texts
function writeToLastRange(texts) {
  const sheet = getSpreadsheet().getSheets()[0];
  sheet.appendRow(texts);
}

// メッセージタイプによって実行する関数を振り分ける
function postReception(postDataEvent) {
  const message = postDataEvent.message;
  switch(message.type) {
    case 'text': writeTextMessage(message);
    break;
    case 'image': saveImage(message);
    break;
    case 'video': // ここは video を受信した時の処理
    break;
  }
}

// 送られてきたテキストメッセージをスプレッドシートに書き込む
function writeTextMessage(message) {
  writeToLastRange([message.text]);
}

// 受信したメッセージから画像を取得する
function getImageByLINE(messageId) {
  const LINE_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN');
  const LINE_END_POINT = 'https://api-data.line.me/v2/bot/message/' + messageId + '/content';
  try {
    const url = LINE_END_POINT;
    const headers = {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN
    };
    const options = {
      'method' : 'get',
      'headers' : headers,
    };
    const res = UrlFetchApp.fetch(url, options);
    // Blob形式で画像を取得し、ファイル名を設定する
    const contentType = res.getHeaders()["Content-Type"];
    const extension = contentTypeToExtension(contentType);
    const fileName = 'postedImageByLine' + extension;
    return imageBlob = res.getBlob().getAs(contentType).setName(fileName)
  } catch(e) {
    Logger.log(e.message);
  }
  return null;
}

// 受信した画像をフォルダに保存する
function saveImage(message) {
  const imgBlob = getImageByLINE(message.id);
  if(imgBlob === null) return;
  const folder = DriveApp.getFolderById(PropertiesService.getScriptProperties().getProperty('IMAGE_FOLDER_ID'));
  folder.createFile(imgBlob);
}

// コンテンツタイプを拡張子にする
function contentTypeToExtension(contentType) {
  return '.' + contentType.substr(contentType.indexOf('/') + 1);
}

受信した画像を保存するソースは上記になります。
さらっと書き換えましたが、13行目などのグローバルな値は、スクリプトプロパティにしました。


忘れていましたが、プロジェクトがデータ(ドライブ内のフォルダーやファイル)にアクセスする場合、
承認が必要な旨が出てきます。
自分で作った(安全な)ものに関しては承認してあげましょう。

ということで、画像を送信してみます。

開発用アカウントに画像を送信する

ドライブ内の所定のフォルダにアップロードできていることを確認。

もちろん開ける。

同様に受信した動画をマイドライブの所定の場所に保存してみます。

function doPost(e) {
  // JSON はパーズしておく
  const postData = JSON.parse(e.postData.contents);
  for(let i = 0; i < postData.events.length; i++) {
    postReception(postData.events[i]);
  }
}

let chatBotSpreadsheet = null;
// スプレッドシートを取得する
function getSpreadsheet() {
  // スプレッドシートの ID
  const SSID = PropertiesService.getScriptProperties().getProperty('SPREAD_SHEET_ID');
  if(chatBotSpreadsheet === null) chatBotSpreadsheet = SpreadsheetApp.openById(SSID);
  return chatBotSpreadsheet;
}

// テキストを最終行に追記する
// @param [string, string...] texts
function writeToLastRange(texts) {
  const sheet = getSpreadsheet().getSheets()[0];
  sheet.appendRow(texts);
}

// メッセージタイプによって実行する関数を振り分ける
function postReception(postDataEvent) {
  const message = postDataEvent.message;
  switch(message.type) {
    case 'text': writeTextMessage(message);
    break;
    case 'image': saveImage(message);
    break;
    case 'video': saveVideo(message);
    break;
  }
}

// 送られてきたテキストメッセージをスプレッドシートに書き込む
function writeTextMessage(message) {
  writeToLastRange([message.text]);
}

// 受信したメッセージから画像を取得する
function getImageByLINE(messageId) {
  const LINE_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN');
  const LINE_END_POINT = 'https://api-data.line.me/v2/bot/message/' + messageId + '/content';
  try {
    const url = LINE_END_POINT;
    const headers = {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN
    };
    const options = {
      'method' : 'get',
      'headers' : headers,
    };
    const res = UrlFetchApp.fetch(url, options);
    // Blob形式で画像を取得し、ファイル名を設定する
    const contentType = res.getHeaders()["Content-Type"];
    const extension = contentTypeToExtension(contentType);
    const fileName = 'postedImageByLine' + extension;
    return imageBlob = res.getBlob().getAs(contentType).setName(fileName)
  } catch(e) {
    Logger.log(e.message);
  }
  return null;
}

// 受信した画像をフォルダに保存する
function saveImage(message) {
  const imgBlob = getImageByLINE(message.id);
  if(imgBlob === null) return;
  const folder = DriveApp.getFolderById(PropertiesService.getScriptProperties().getProperty('IMAGE_FOLDER_ID'));
  folder.createFile(imgBlob);
}

// 受信したメッセージから動画を取得する
function getVideoByLINE(messageId) {
  const LINE_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN');
  const LINE_END_POINT = 'https://api-data.line.me/v2/bot/message/' + messageId + '/content';
  try {
    const url = LINE_END_POINT;
    const headers = {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN
    };
    const options = {
      'method' : 'get',
      'headers' : headers,
    };
    // Blob形式で画像を取得し、ファイル名を設定する
    const res = UrlFetchApp.fetch(url, options);
    const contentType = res.getHeaders()["Content-Type"];
    const extension = contentTypeToExtension(contentType);
    const fileName = 'postedVideoByLine' + extension;
    return videoBlob = res.getBlob().getAs(contentType).setName(fileName)
  } catch(e) {
    Logger.log(e.message);
  }
  return null;
}

// 受信した動画をフォルダに保存する
function saveVideo(message) {
  const videoBlob = getVideoByLINE(message.id);
  if(videoBlob === null) return;
  const folder = DriveApp.getFolderById(PropertiesService.getScriptProperties().getProperty('VIDEO_FOLDER_ID'));
  folder.createFile(videoBlob);
}

// コンテンツタイプを拡張子にする
function contentTypeToExtension(contentType) {
  return '.' + contentType.substr(contentType.indexOf('/') + 1);
}

ソースコード全文です。
画像の保存とそんなに変わらないです。
自分のアカウントから開発アカウントに送ってみます。

開発用アカウントに動画を送信する。

所定のフォルダに保存されることを確認。

再生してみる。

まとめ

LINE 公式アカウント(開発アカウント)が受信したメッセージデータ(テキスト、画像、動画)を GAS で操作してみました。
テキストの場合は受信したメッセージデータからテキストを取得できます。
他の画像、動画に関しては、メッセージデータの id と LINE エンドポイントと GAS を利用してファイル化することができます。

前回と今回、受信したものをドライブに保存するようにしていますが、セキュリティ面を考えると色々問題があります。
例えば、受信したメッセージをそのままスプレッドシートに直接書き込んでいるので、スプレッドシートの関数を受信すると、そのままスプレッドシートの関数を実行してしまいます。

先頭に = を付与して関数を送信する。

関数が直接書き込まれる。

正しい関数なら当然実行された状態で送信される。

もしもデータを上書きされたり、開けなくなるほどの関数を実行されてしまったら...?
悪質な画像や、ウイルスを動画として受信してしまったら...?
ということで、本番運用する際は本当に気をつけましょう。