GASを使い、LINE Botで送信した画像をOCRし、文字情報をLINE Botへ返信 + GoogleSpreadsheetへ記録する。

写真をOCRして文字情報を返信してくれるサービスはありますが、LINE WORKSが必要であったり、他との連携が難しかったりします。そこで今回これと同様の機能を再現するとともに、同時にGoogleスプレッドシートへも記録するというLINE Botを作成してみました。
これは、手書きのメモなどを取り込んでテキスト化する、それを整理して保存しておくことにも役立つと思います。またGoogle以外のサービスと連携して使用すれば、幅広く応用できるものだと思います。

![やりたいこと模式図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/2b85d6d3-4eed-558d-5f17-840c6b5575e5.png)
![OCR動作の様子2-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/bb2467b9-ecb6-1a5b-bc6f-f6b08054ebb6.gif)
（この動画はイメージです。実際には処理にはそれぞれもう数秒かかっています。）



●LINE Botと1対1、もしくはLINE Botが参加しているグループに画像が送信されると、予め指定しておいたGoogle Driveのフォルダーに画像を自動保存する。
●画像のファイル名を「LINE画像_YYMMDD_HHmmss」に自動設定し、いつ保存された画像がわかるようにする。
　　例)「LINE画像_200916_210733」→2020/9/16の21時7分33秒に保存された画像。
●画像以外を送信すると『画像を送信してください』と自動返信する。
●画像が保存されたら、その画像をGoogleドキュメントへ送りOCRをかける。
●OCRして得られたテキストを、LINE Botへ返信、Google スプレッドシートへ書き込む。

# 作成の流れ

**１．LINE Developersコンソール内での準備**
　①チャネルを作成
　②チャネルアクセストークンを発行
**２．Google Driveでの準備**
　フォルダーIDを確認
**３．画像を自動保存するLINE BotのGASを作る**
　①スクリプトエディタを開く
　②Google Apps Scriptを開く
　③プロジェクト名とスクリプト名を記入する
　④Moment.jsライブラリを導入する
　⑤Drive APIを導入する
　⑥GASへコードを書きこむ
**４．GASとLINE Messaging APIを接続する**
　①デプロイ（Deploy 公開）
　②LINE Developersコンソールに、Webhook URLを登録する
**５．LINE Botの設定**
　①LINE Botを友だち追加する
　②リッチメニューの設定


# 作成手順
## 1．LINE Developersコンソール内での準備　～3．画像を自動保存するLINE BotのGASを作る　4.Moment.jsライブラリを導入する まで
下記事内で、画像付きでとても詳細に紹介されていますので参照しながら作成してください。
【シリーズ第1話】Google Driveに画像を自動保存するLINE BotをGASで作ろう (準備編)
　　　　https://www.yukibnb.com/entry/linemessagingapi_save_image
【シリーズ第2話】Google Driveに画像を自動保存するLINE BotをGASで作ろう (実践編)
　　　　https://www.yukibnb.com/entry/linemessagingapi_gas_save_image
【超便利！】GASのややこしい日付&時刻処理はMoment.jsで解決！
　　　　https://www.yukibnb.com/entry/2019/03/02/152302


## ３．画像を自動保存するLINE BotのGASを作る
#### 　⑤Drive APIを導入する
GASでOCRを簡単に使えるんですか！？
　https://zenn.dev/harachan/articles/d910ef8b89720b
を参照しながら　Drive APIを導入する。

**注意：**（2024.3.9追記）
　DriveAPIは、今はv3(Ver.3)となっていますが、**v2(Ver.2)でないとうまくOCRが働かない**そうです。Drive API作成の際、バージョン選択は初めはv3になっているので、クリックして "v2" を選んでください。
　〔最近この記事を利用し実装された方より情報提供がありました〕
（追記ここまで）

左側リストの「サービス」横のプラスマーク「+」を押すと追加するサービスを選べる画面が表示されるので、「Drive API」をクリックして選択し、追加をクリックしてください。
![GASのDrive API導入方法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/195270ec-25f1-539d-af0a-985ac287c077.png)

#### 　⑥GASへコードを書きこむ
Google App Scriptに以下のコードをコピー＆ペースト。
チャネルアクセストークン、Folder IDを自分のものに書き換える。

```javascript
//★★LINE Messaging APIのチャネルアクセストークン★★
var LINE_ACCESS_TOKEN = "自分のチャネルアクセストークンを入れてください";

//★★画像を保存するフォルダーID★★
var GOOGLE_DRIVE_FOLDER_ID = "Folder IDを入れてください";

  //ファイル名に使う現在日時をMomentライブラリーを使って取得
  var date = Moment.moment(); //現在日時を取得
  var formattedDate = date.format("YYYYMMDD_HHmmss");

//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);

  //受信したメッセージ情報を変数に格納する
  var reply_token　= json.events[0].replyToken; //reply token
  var messageId = json.events[0].message.id; //メッセージID
  var messageType = json.events[0].message.type; //メッセージタイプ

  //LINEで送信されたものが画像以外の場合、LINEで返信し動作を終了する
  if(messageType !== "image"){
    var messageNotImage = "画像を送信してください"
    //変数reply_tokenとmessageNotImageを関数sendMessageに渡し、sendMessageを起動する
    sendMessage(reply_token, messageNotImage)
    return;
  }

  var LINE_END_POINT = "https://api-data.line.me/v2/bot/message/" + messageId + "/content";
  
  //変数LINE_END_POINTとreply_tokenを関数get_ocr_Imageに渡し、get_ocr_Imageを起動する
  getImage(LINE_END_POINT, reply_token);
  }


//Blob形式で画像を取得する
function getImage(LINE_END_POINT, reply_token){
  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")

  //変数imageBlobとreply_tokenを関数saveImageに渡し、saveImageを起動する
  save_ocr_Image(imageBlob, reply_token)

  } catch(e) {
  //例外エラーが起きた時にログを残す
  Logger.log(e.message);
  }
}

//画像をGoogle Driveのフォルダーに画像を保存（アップロード）、OCRをかけテキストを取得。
//保存された画像、作成されたドキュメントファイルは残す。
//OCRで得られたテキストをLine Botへ返信する。
function save_ocr_Image(imageBlob, reply_token){
  try{
      //画像をGoogle Driveのフォルダーに画像を保存（アップロード）
    var folder = DriveApp.getFolderById(GOOGLE_DRIVE_FOLDER_ID);
    var file = folder.createFile(imageBlob);

      //ここからOCRをかけていく
    let a = Drive.Children.list(GOOGLE_DRIVE_FOLDER_ID)
    
    // 設定事項を書いていく
    let resource = {
      title: "OCR結果" + formattedDate // 生成されるGoogleドキュメントのファイル名
    };
    let option = {
      "ocr": true,// OCRを行うかの設定です
      "ocrLanguage": "ja",// OCRを行う言語の設定です
    }
    
    // 取得したファイル一覧の最初のもののfileIdを変数に格納します
    let fileId = a.items[0].id
    // 指定したfileIdのファイルをコピーします
    let image = Drive.Files.copy(resource, fileId, option)
    // コピー先ファイルにはOCRのデータが含まれているのでテキストを取得します
    let text = DocumentApp.openById(image.id).getBody().getText();

    // アップロードした写真、作成したOCRファイルが不要であれば、以下のコードをここに書いて削除する（先頭の"//"を消す）
    //Drive.Files.remove(image.id) //作成したOCRファイルを削除
    //Drive.Files.remove(fileId)   //アップロードした写真を削除
    
    // OCRで得られたテキストを、現在のスプレッドシートの一番最終行+1行に張り付ける。
    var ss = SpreadsheetApp.getActiveSpreadsheet()
    var sheet = ss.getActiveSheet()
    var lastRow = sheet.getLastRow();
    sheet.getRange(lastRow+1, 1).setValue(text)

    // OCRで得られたテキストを、Line Botへ返信する。
    var message = "画像をOCRしスプレッドシートに保存しました。\n"+text;
    //変数reply_tokenとmessageを関数sendMessageに渡し、sendMessageを起動する
    sendMessage(reply_token, message)

  } catch(e){
    //例外エラーが起きた時にログを残す
    Logger.log(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);
}
```

## ４．GASとLINE Messaging APIを接続する
#### ①デプロイ（Deploy 公開）
　　　GASを公開可能にする。
　　**●新しいエディタの場合**
　　　右上の「デプロイ」ボタンを押し、「新しいデプロイ」を選択。
　　　開いたウインドウの左上の「種類の選択」の⚙を押し、「ウェブアプリ」を選択。
　　　出現した画面下のアクセスできるユーザーを「全員」に変更し、
　　　右下の「デプロイ」ボタンを押せば公開終了。
　　　数秒後に開いたウインドウの**ウェブアプリURL**をコピーする。
![GASのデプロイ方法（新）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/e5816a9b-478e-9c35-2519-bfdb72c5d0dc.png)


　　**●以前のエディタの場合**
　　　右上の「以前のエディタへ切替え」ボタンを押す。
　　　切り替わったら上のメニューバーの「公開」メニューから
　　　「ウェブアプリケーションとして導入」を選択。
　　　アプリケーションにアクセスできるユーザーを「全員(匿名ユーザーを含む)」
　　　にして導入ボタンを押せば、公開完了。
　　　最後に現在のウェブアプリケーションのURL（**Current web app URL**）の画面が出るので、コピーする。
![GASのデプロイ方法（旧）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/517617cb-ccf6-6c22-81f2-89895ef4547f.png)

#### ②LINE Developersコンソールに、Webhook URLを登録する
　　ウェブアプリURL（以前のエディタの場合は、現在のウェブアプリケーションのURL
　　（Current web app URL））を、LINE Developerコンソール画面を開き、
　　　Webhook設定に張り付ける。
　　　　（node(VSCode)と違い”/webhook”は後ろに付けない。）
　　　**Webhook URLの下にある、「Webhookの利用」を”有効”にする`（忘れがちなので注意！）`**[2021/12/16追記] 
![Webhook設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/cacd23b0-45b6-22fb-13bc-59daaf4f17ff.png)

## ５．LINE Botの設定
#### 　①LINE Botを友だち追加する
LINE Botを友だち追加する
　LINE Developersの作成したLINE Botの、Messaging APIタグの上の方にあるQRコードを、自分のLINEで読み込み、友だち追加する。
![QRコード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/700e3354-90ee-657b-51f8-29bf3c9f9913.png)

#### 　②リッチメニューの設定
これは行わなくても良いですが、設定すると使い勝手がよくなります。
テンプレートから2枠のリッチメニューを選択し、
A枠、B枠ともタイプ：リンク　とし、
　A枠には　（カメラを開くURL）　　　https://line.me/R/nv/camera/
　B枠には　（写真リストを開くURL）　https://line.me/R/nv/cameraRoll/multi
を記入する。

![リッチメニューの設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/909ebfd4-bc4c-1ef8-2405-155f326aa51a.png)



# OCR機能の補完
(2024.2.14 追記）

googleのOCR機能は優秀と思いますが、誤認は多々あります。
私が気づく範囲では以下の問題点があります。

●行の終わりで改行されず文章が繋がるが、半角スペースが入る。
●「，」「．」「：」「（」「）」等は全角で認識出来る時と出来ない時がある。

手動で修正、ワードやエクセルの「置換」機能を利用して修正など、地道に修正する方法はあります。
どうせならば自動でそれができないかと考えたところ、
OCRで抽出したテキストをに対し、GAS上で「置換」機能を利用して、自動で文章を整形する方法を思いついたので、記載します。

```javascript
............
............

    // 取得したファイル一覧の最初のもののfileIdを変数に格納します
    let fileId = a.items[0].id
    // 指定したfileIdのファイルをコピーします
    let image = Drive.Files.copy(resource, fileId, option)
    // コピー先ファイルにはOCRのデータが含まれているのでテキストを取得します
    let text = DocumentApp.openById(image.id).getBody().getText();

    // アップロードした写真、作成したOCRファイルが不要であれば、以下のコードをここに書いて削除する（先頭の"//"を消す）
    //Drive.Files.remove(image.id) //作成したOCRファイルを削除
    //Drive.Files.remove(fileId)   //アップロードした写真を削除

............
............
```

先ほどのコードの中頃にある、上記部位の
「let text = DocumentApp.openById(image.id).getBody().getText();」
の部分を、以下のコードに置き換えてください。

```javascript

    var text_O = DocumentApp.openById(image.id).getBody().getText();

    var text_R01 = text_O.replace(/([^ -~｡-ﾟ]),/g, '$1，')   //全角文字直後の、半角「,」を全角「，」へ置換する。
                        .replace(/([^ -~｡-ﾟ])\./g, '$1．')   //全角文字直後の、半角「.」を全角「．」へ置換する。
                        .replace(/(\d)\．/g, '$1\.')        //数字後の全角「．」を半角「.」へ置換する。
                        .replace(/([ -~｡-ﾟ])、/g, '$1, ')    //半角英文字・数字直後「、」を「，」へ置換する。
                        .replace(/([ -~｡-ﾟ])。/g, '$1. ')    //半角英文字・数字直後「、」を「，」へ置換する。
                        .replace(/([^ -~｡-ﾟ])\(/g, '$1（')   //全角文字直後の、半角「(」を全角「（」へ置換する。
                        .replace(/([^ -~｡-ﾟ])\)/g, '$1）')   //全角文字直後の、半角「)」を全角「）」へ置換する。
                        .replace(/\(([^ -~｡-ﾟ])/g, '（$1')   //全角文字直前の、半角「(」を全角「（」へ置換する。
                        .replace(/\)([^ -~｡-ﾟ])/g, '）$1')   //全角文字直前の、半角「)」を全角「）」へ置換する。
                        .replace(/([^ -~｡-ﾟ])\:/g, '$1：')  //全角文字直後の、半角「:」を全角「：」へ置換する。
                        .replace(/([^ -~｡-ﾟ]) /g, '$1');     //全角文字直後の、半角スペース「 」を消去する。

      if ( text_R01.match(/(，|．)/)) {
        //文章中に全角「，」、「．」がある場合は、句読点「、」、「。」を「，」、「．」にそろえる。
      var text_R02 = text_R01.replace(/([^ -~｡-ﾟ])、/g, '$1，').replace(/([^ -~｡-ﾟ])。/g, '$1．');

      }else{
        //文章中に全角「，」、「．」がない場合は、そのまま。
        var text_R02 = text_R01;
      }

    var text = text_R02;
    
```

その他の方法としては、
google spread sheetを利用して、
例えば、A1セルにOCR結果テキストを送信し、別セルに、

=substitute(substitute(substitute(substitute(A1,".","．"),",","，"),"、","，"),"。","．")

と記入すると、「置換」機能が使えます。


(2024.2.14　追記ここまで)



# 工夫した箇所、苦労した箇所
①　はじめは「GASでOCRを簡単に使えるんですか！？」を基本に今まで作成した記事のコードを組み込み動かそうと試みましたが上手くいきませんでした。再度ネット検索にて「Google Driveに画像を自動保存するLINE BotをGASで作ろう」に出会い、この2つの記事にあるコードの相性が良く、それまで難渋していたのが嘘のように、急にコードの骨格が出来上がりました。

②　アップロードした画像ファイル名、作成したファイル名を、更新日時を入れ整理して保存することができる。
「Google Driveに画像を自動保存するLINE BotをGASで作ろう」、「【超便利！】GASのややこしい日付&時刻処理はMoment.jsで解決！」に記載されていたアイディアをそのまま頂きました。

③ OCRして得られたテキストを、LINE Botへ返信することにより、LINE Bot上でOCR内容をすぐに確認ができ、すぐに活用しやすい。また同時にGoogle スプレッドシートへ書き込むことにより、データの整理ができ、その後の活用に繋げやすいと思います。

④ このLINE Botは文字入力を必要としないことから開いたらすぐに画像を送れるようにすれば使いやすいと考えました。そこでリッチメニューを設定することによりそれを実現できました。LINE Botを開いてから写真を送信するまでのプロセスが1つ減らせるのみならず、ボタンが大きく絵が付いているので分かりやすく考えることなく自然に使えるようになりました。

# 参考にしたホームページ
【シリーズ第1話】Google Driveに画像を自動保存するLINE BotをGASで作ろう (準備編)
　　　　https://www.yukibnb.com/entry/linemessagingapi_save_image
【シリーズ第2話】Google Driveに画像を自動保存するLINE BotをGASで作ろう (実践編)
　　　　https://www.yukibnb.com/entry/linemessagingapi_gas_save_image
【超便利！】GASのややこしい日付&時刻処理はMoment.jsで解決！
　　　　https://www.yukibnb.com/entry/2019/03/02/152302
GASでOCRを簡単に使えるんですか！？
　　　　https://zenn.dev/harachan/articles/d910ef8b89720b
LINE URLスキームでLINEの機能を使う
　　　　https://developers.line.biz/ja/docs/messaging-api/using-line-url-scheme/

# ひとりごと
この仕組みを作っている間に、様々な検索をしてもこの仕組みの記事は見つからなかったのですが、この記事を書いているとき、何気なく再度検索したところ、すでに全く同じものを作っている方が沢山いらっしゃいました。考えることは皆さん同じなんですね。しかしこれらの記事に出会う前に、上の詳細に記載されている記事に出会うことができ、新しい知識を得られたのは幸運でした。
【GAS】画像を送ると文字に起こすLINEbot【OCR】
https://qiita.com/YasumiYasumi/items/49479bfaa683fefd09c5
LineBotを使用してOCRで画像を文字にしてみる
https://newtechnologylifestyle.net/linebotocr/
GAS｜LINE bot（ボット）で画像から文字起こし（OCR）する｜秘書 bot｜活用事例｜非エンジニアのための「Google Apps Script」
https://note.com/cutboss/n/n7da37c99b6cc
GoogleAppsScriptとLINE Botで自分用の「文字起こし君」を作ってみた
https://synapse-diary.com/?p=5379
GASとLINE Messaging APIで「My『文字起こし君』」を作る！（AIじゃなくてOCRで！）
https://walkingmask.hatenablog.com/entry/2018/04/26/011611
等々

# OCR機能を応用した作成物

（2024.3.27追記）
この記事を応用し、以下のものを作成しました。合わせてお読みいただけると嬉しいです。

「文字練習bot」～canvasで文字を書き、OCRで読み取る～
https://qiita.com/21HideK/items/ce9f1487420b5ca4a4c3