LoginSignup
8
9

More than 3 years have passed since last update.

研究室の書籍貸出簿をJavascriptとGoogle Apps Scriptと書誌情報APIで.

Last updated at Posted at 2019-11-12

その1:本のバーコードを読んで貸出記録をつけるシステム
その2:返却期限すぎても返却されていない本のリマインドメールを出すシステム
その3:貸出簿をつける時のエラーチェック(スプレッドシートの学生情報DBに問い合わせするシステム)
その4 : 返却処理のシステム

学生が本を借りていくのは良いけど、誰にどの本を貸したのかがわからなくなることが結構ある。
学生の方も言わないと返してこないことが多い。
ということで書籍貸し出しのシステムを作りたい。

構想

こんな感じのシステムだったら良いな。

  • 本棚にQRコードをつけて、それを読み込むとWebフォームに飛ぶ。
  • Webフォームからスマホのカメラを起動させ、スマホで本についてるISBNのバーコードを読み取る
  • 読み取ったバーコードの数値から図書検索APIを使って書籍名を検索し、フォームに入力
  • 本人の情報を入力させてGoogleスプレッドシートに飛ばす。貸し出し手続き完了。
  • 貸し出し日も自動的に管理され、2週間経過すると返却処理が完了するまでリマインドメールが配信される。
  • 返却は同じく本棚に別のQRコードを貼っておき、それを読み込ませて返却用Webフォームにアクセスする。
  • 本のバーコードを読み込んで、貸し出しリストを検索し、返却日を記録する。

調べないといけないこと.
- 図書情報取得のAPI
- JavascriptでスマホのWebブラウザからバーコードを読む方法
- Googleスプレッドシートをjavascriptから読み書き

参考にできそうなページ
Javascriptのバーコードライブラリ
https://qiita.com/k-murayama/items/eddcc974bd0dd3a214ed
Google Books APIでISBNコードから書籍検索するスクリプト
http://azuma006.hatenablog.com/entry/2015/07/19/220939
Googleスプレッドシートをjsから読み書きしてみた
http://derax.hateblo.jp/entry/2015/09/25/170829
【doPost編】GASとHTMLでフォームを作成し、スプレッドシートに記録する方法1
https://productivityresearch.net/programing/116/

インタフェースの実装

バーコードの読み取り

インタフェースとなるhtmlは大学のサーバの研究室のページに置きたかった.
けど,大学のサーバはあくまホームページ公開用の領域を与えてくれるだけなのでJavasacripitしか使えない.
何とかJavascriptだけでできるライブラリがないかといことで探して,見つけたのが以下のライブラリ.
https://github.com/EddieLa/JOB/tree/gh-pages

そのまま移植して動いてくれた.

読み取ったISBNで図書検索APIで検索

続いて読み取ったバーコードで図書検索を掛ける.
さっきそのまま移植したindex.htmlの中の<script>を読み解いてみる.(というほどでもないが)
以下の部分がバーコードの数値を取ってるところ.

index.html
・・・
JOB.SetImageCallback(function(result) {
    if(result.length > 0){
        var tempArray = [];
        for(var i = 0; i < result.length; i++) {
            tempArray.push(result[i].Format+" : "+result[i].Value);
            /******** 
             result[i].valueが読み取ったコードになる.
       ここに図書検索の処理を書き込めばよいはず.
            *********/
        }
        Result.innerHTML=tempArray.join("<br />");
    }else{
        if(result.length === 0) {
            Result.innerHTML="Decoding failed.";
        }
    }
});

ということで,上記の箇所に図書検索apiを書き込む.

書籍情報を取れるAPIには,国会図書館API,Google Books API,OpenBD APIがある.
いずれも簡単な文法でISBN番号で書籍検索できる.
https://qiita.com/Hidekazu-Karino/items/3c2edbadbcbf1551c6cf)

国会図書館が当然ながら和書に関しては一番豊富だとおもうんだけど,CROSの問題でうまくいかなかった.
PHP使えたり,自分でApacheサーバー立てたりしてるならどうにかできるんだけど,今回サーバーを自前で用意してるわけではないのでこの問題は解消できず.
https://qiita.com/310kent0/items/ac04d35befffac6b695e)

仕方がないので,OpenBDとGoogle Booksを併用することにした.
ということで以下のようなコード.
#Booktitle#BookAuthorはhtmlの中のinputタグのid.
http://azuma006.hatenablog.com/entry/2015/07/19/220939)

index.html
JOB.SetImageCallback(function(result) {
    if(result.length > 0){
    var tempArray = [];
    for(var i = 0; i < result.length; i++) {
        tempArray.push(result[i].Format+" : "+result[i].Value);
        const isbn = result[i].Value;
        $("#BookTitle").val("");
        $("#BookAuthor").val("");
        if(9780000000000<isbn && isbn <9799999999999) {//ISBNは978か979で始まるので,それ以外の数字は処理からはじく.
            const googleurl = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn; //Google BooksのAPI
            //const ndlurl = "http://iss.ndl.go.jp/api/sru?operation=searchRetrieve&recordSchema=dcndl_simple&query=isbn="+isbn; //国会図書館API
            const openbdurl = "https://api.openbd.jp/v1/get?isbn=" + isbn; //OpenBDのAPI
            var flag_getbookinfo = false;
            //国会図書館API使いたかったけどエラーで使えず.
            // $.ajax(ndlurl).done(function(data, textStatus, jqXHR){
            //  var title = $(data).find('dcterms\\:title').eq[0].innerHTML;
            //  var creator = $(data).find('dcterms\\:creator').eq[0].innerHTML;
            //  $("#BookTitle").val(title);
            //  $("#BookAuthor").val(creator);                          
            // });
            $.getJSON(googleurl, function(data) {//Google Booksで検索
                if(data.totalItems) {
                    // 該当書籍が存在した場合、JSONから値を取得して入力項目のデータを取得する
                    if(flag_getbookinfo==false){        
                        $("#BookTitle").val(data.items[0].volumeInfo.title);
                        $("#BookAuthor").val(data.items[0].volumeInfo.authors[0]);
                        $("#Error").text('GoogleBooksより情報取得.');
                        flag_getbookinfo = true;
                    }
                }else{
                    $("#Error").append("<p>GoogleBooksでは見つかりませんでした</p>")
                }
            });
            $.getJSON(openbdurl, function(data) {//OpenBDで検索
                if(data[0]) {
                // 該当書籍が存在した場合、JSONから値を取得して入力項目のデータを取得する
                    if(flag_getbookinfo==false){        
                        $("#BookTitle").val(data[0].summary.title);
                        $("#BookAuthor").val(data[0].summary.author);
                        $("#Error").text('OpenBDより情報取得.');
                        flag_getbookinfo = true;
                    }
                }else{
                    $("#Error").append("<p>OpenBDでは見つかりませんでした</p>");
                }
            }); 
        }
    }
});

とりあえずインタフェースは完成.

以下がソースコード.

index.html
<!DOCTYPE html>
<meta charset=utf-8>
<html lang="ja">
    <head>
        <title>藤野ゼミの本の貸し出しフォーム</title>
        <meta name="robots" content="noindex" />
    </head>
    <body>
        <div id="container">
            <p>以下のボタンを押してカメラを起動させ,本のバーコードを撮影してください.</p>
            <p><input id="Take-Picture" type="file" accept="image/*;capture=camera" /></p>
            <p><canvas width="240" id="picture"></canvas></p>
            <p id="textbit"></p>
            <p id="Error">該当する書籍がみつからない場合はISBN(978または979から始まる13桁の数字)が正しく読めているか確認してください.読めている場合にはデータベースにない書籍ですので,手動で書籍名を入力してください.</p>
            <form method="POST" action="...">
                <p>書  籍  名:<input id ="BookTitle" name="BookTitle" type ="text"></input></p>
                <p>著  者  名:<input id ="BookAuthor" name="BookAuthor" type ="text"></input></p>
                <p>名     前:<input id ="StudentName" name ="StudentName" type ="text"></input></p>
                <p>学 籍 番 号:<input id ="StudentNo" name ="StudentNo" type="text"></input><br/>※末尾の0は除いて下さい.</p>
                <p><input type="submit" value="貸出" ></input></p>
            </form>
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
        <script type="text/javascript" src="JOB.js"></script>
        <script type="text/javascript">
            var takePicture = document.querySelector("#Take-Picture"),
            showPicture = document.createElement("img");
            Result = document.querySelector("#textbit");
            var canvas =document.getElementById("picture");
            var ctx = canvas.getContext("2d");
            JOB.Init();
            JOB.SetImageCallback(function(result) {
                if(result.length > 0){
                    var tempArray = [];
                    for(var i = 0; i < result.length; i++) {
                        tempArray.push(result[i].Format+" : "+result[i].Value);
                        const isbn = result[i].Value;
                        $("#BookTitle").val("");
                        $("#BookAuthor").val("");
                        if(9780000000000<isbn && isbn <9799999999999) {
                            const googleurl = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + isbn;
                            const openbdurl = "https://api.openbd.jp/v1/get?isbn=" + isbn;
                            var flag_getbookinfo = false;
                            $.getJSON(googleurl, function(data) {//GoogleBooksで検索
                                if(data.totalItems) {
                                    if(flag_getbookinfo==false){        
                                        $("#BookTitle").val(data.items[0].volumeInfo.title);
                                        $("#BookAuthor").val(data.items[0].volumeInfo.authors[0]);
                                        $("#Error").text('GoogleBooksより情報取得.');
                                        flag_getbookinfo = true;
                                    }
                                }else{
                                    $("#Error").append("<p>GoogleBooksでは見つかりませんでした</p>");

                                }
                            });
                            $.getJSON(openbdurl, function(data) {//OpenBDで検索
                                if(data[0]) {
                                    if(flag_getbookinfo==false){        
                                        $("#BookTitle").val(data[0].summary.title);
                                        $("#BookAuthor").val(data[0].summary.author);
                                        $("#Error").text('OpenBDより情報取得.');
                                        flag_getbookinfo = true;
                                    }
                                }else{
                                    $("#Error").append("<p>OpenBDでは見つかりませんでした</p>");
                                }
                            }); 
                        }
                    }
                    Result.innerHTML=tempArray.join("<br />");                  
                }else{
                    if(result.length === 0) {
                        Result.innerHTML="Decoding failed.";
                    }
                }
            });
            JOB.PostOrientation = true;
            JOB.OrientationCallback = function(result) {
                canvas.width = result.width;
                canvas.height = result.height;
                var data = ctx.getImageData(0,0,canvas.width,canvas.height);
                for(var i = 0; i < data.data.length; i++) {
                    data.data[i] = result.data[i];
                }
                ctx.putImageData(data,0,0);
            };
            JOB.SwitchLocalizationFeedback(true);
            JOB.SetLocalizationCallback(function(result) {
                ctx.beginPath();
                ctx.lineWIdth = "2";
                ctx.strokeStyle="red";
                for(var i = 0; i < result.length; i++) {
                    ctx.rect(result[i].x,result[i].y,result[i].width,result[i].height); 
                }
                ctx.stroke();
            });
            if(takePicture && showPicture) {
                takePicture.onchange = function (event) {
                    var files = event.target.files;
                    if (files && files.length > 0) {
                        file = files[0];
                        try {
                            var URL = window.URL || window.webkitURL;
                            showPicture.onload = function(event) {
                                Result.innerHTML="";
                                JOB.DecodeImage(showPicture);
                                URL.revokeObjectURL(showPicture.src);
                            };
                            showPicture.src = URL.createObjectURL(file);
                        }
                        catch (e) {
                            try {
                                var fileReader = new FileReader();
                                fileReader.onload = function (event) {
                                    showPicture.onload = function(event) {
                                        Result.innerHTML="";
                                        JOB.DecodeImage(showPicture);
                                    };
                                    showPicture.src = event.target.result;
                                };
                                fileReader.readAsDataURL(file);
                            }
                            catch (e) {
                                Result.innerHTML = "Neither createObjectURL or FileReader are supported";
                            }
                        }
                    }
                };
            }
        </script>
    </body>
</html>

Google スプレッドシードとGoogle Apps Scriptの準備

インタフェースはできたので,FormのPostを受け取るスプレッドシードを準備する.

スプレッドシードの準備

ゼミで作ってる共用アカウントのドライブ上でスプレッドシードを作る.
名前はlentabookとしておく.
項目は以下の通りにしておく.
image.png

Google Apps Scriptを作る

スプレッドシードの上にあるツールスクリプトエディタを起動.
最初はmyFunction()のスケルトンが表示されている.
ここに,以下のコードを追記.

formからPostされると,Google Apps ScriptではdoPost(e)が起動する.
http://derax.hateblo.jp/entry/2015/09/25/170829)

eはポストされてきたデータのオブジェクト.このオブジェクトの中のparameterっていうメンバオブジェクトに,Form内の各input要素のname属性がそのまま変数名となって各データが入ってる.
データはそのままでは型がundifinedなので,toString()で文字列型する.
https://productivityresearch.net/programing/116/)

SpreadsheetApp.openById() の引数は各スプレッドシードのURLにある以下の*****の部分の文字列.
https://docs.google.com/spreadsheets/d/*******/edit#gid=0

Google Apps Script
function doPost(e) {
  var time =new Date();
  var booktitle = e.parameter.BookTitle.toString();
  var bookauthor= e.parameter.BookAuthor.toString();
  var studentname=e.parameter.StudentName.toString();
  var studentno = e.parameter.StudentNo.toString();

  var ss = SpreadsheetApp.openById('******');
  var sheet = ss.getSheetByName("シート名");

  sheet.appendRow([time, booktitle,bookauthor,studentname,studentno]);
}

記録完了の通知ページ

上記まででデータは記録されるけど,これだけだと記録完了後に画面上で「スクリプトが完了しましたが、何も返されませんでした。」と表示されてしまう.
ということで,記録完了を通知するページを作る.

スクリプトエディタのファイルNewHTMLとすると,新しいhtmlがプロジェクトの中に作られる.
それを以下のように編集.
一応,ここで返却日も伝えておく.そのためにスクリプトも含めてみた.

reportinput.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>貸出処理完了</h1>
    <h2>
      <p>貸出期間は原則2週間です.</p>
      <p>(返却予定日:<span id="deadline"></span></p>
      <p>できるだけ守るようお願いします. 藤野</p>
    </h2> 
    <script type="text/javascript">
    document.getElementById("deadline").innerHTML = getNow();
    function getNow() {
      var dt = new Date();
      dt.setDate(dt.getDate()+14);
      var year = dt.getFullYear();
      var mon = dt.getMonth()+1; //1を足すこと
      var day = dt.getDate();
      //出力用
      var s = year + "" + mon + "" + day + "" ; 
      return s;
    }
    </script>

  </body>
</html>

んで,doPost()の末尾に以下のコードを追記.
https://www.symmetric.co.jp/blog/archives/1087)

Google Apps Script
  var html = HtmlService.createTemplateFromFile('reportInput');
  return html.evaluate();

デプロイ

エディタの公開ウェブアプリケーションとして導入を選択.
でてくるダイアログボックスでExcecute the app as:のところをMeに,Who has access to the appを 全ユーザ anyoneに設定.
※追記 全ユーザにするとあくまでGoogleのアカウントにログインしないと使えなかった。本当に誰でも使えるようにするにはanyoneでないとダメだった。

(以下は,すでに一度デプロイした後の画面.初めての場合とはCurrent web app URLのところがあるかないかと,ボタンの表示)
image.png

んで,公開(更新)ボタンを押すと,AppのURLが表示される.それをコピーしておく.

インタフェース画面のformにappのURLを追記.

先ほど作成したindex.htmlのformのActionにAppのURLをコピーする.
*****の箇所はAppのURLに含まれている乱数っぽい文字列.ここが多分Appごとに一意になってる.

<form method="POST" action="https://script.google.com/macros/s/***************/exec">
    <p>書  籍  名:<input id ="BookTitle" name="BookTitle" type ="text"></input></p>
    <p>著  者  名:<input id ="BookAuthor" name="BookAuthor" type ="text"></input></p>
    <p>名     前:<input id ="StudentName" name ="StudentName" type ="text"></input></p>
    <p>学 籍 番 号:<input id ="StudentNo" name ="StudentNo" type="text"></input><br/>※末尾の0は除いて下さい.</p>
    <p><input type="submit" value="貸出" ></input></p>
</form>

完成した貸出システム

ファイルを選択ボタンを押すと,カメラを起動させることができる.
バーコードを撮影すると,バーコードを読みこんで書籍情報を検索して,テキストボックスに書籍情報を入力してくれる.
image.png

とりあえず名前にはtest,学籍を12345678と入力して貸出を押す.
しばらく待つが,ほどなくして,以下の画面が表示される.
image.png

んで,スプレッドシードの方では次のように記録されている.
image.png

ということで,きちんと動作することを確認!!

残された課題

  • 返却期日が過ぎた貸出案件について,定期的に監視してリマインドメールを出す機能.
  • 返却処理用システムの作成.
  • ISBM-10しか乗ってない本の処理.
8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9