本のバーコードを読み取ってNotionで読書録を作成するアプリを作ってみた

# はじめに

はじめまして。インディーゲームを作っております、nyorokoと申します。
ゲームづくりの他に読書が好きで、「読書録を簡単に作成・管理することはできないか？」という問題意識があり、タイトルの通りのアプリを作ってみました。

## 2022/5/23
思ったよりも反響があったため、加筆した第二弾を公開しました！

https://zenn.dev/nyoroko/articles/6b6b58c044505e

# 完成したもの

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">以前Notion APIを用いて、<br><br>本のバーコードを読み取る<br>→ISBNを取得する<br>→ISBNから本の情報を取得する<br>→Notionのデータベースに追加する<br><br>という一連の動作を行うアプリを開発しました💪🏾(´･_･`💪🏾) <a href="https://t.co/GqPBPV7sin">pic.twitter.com/GqPBPV7sin</a></p>&mdash; nyoroko (@nyoroko_nyoro) <a href="https://twitter.com/nyoroko_nyoro/status/1495726866845605890?ref_src=twsrc%5Etfw">February 21, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# Notionとは？

[Notion公式サイト](https://www.notion.so/ja-jp/product)

Notionとは、タスク管理やメモ等を一元的に行うことのできるアプリです。
Evernoteと似ていると思うのですが、無料で複数のデバイスから使用可能であるなどの違いがあります。
私はPC、iPhone、iPadなど様々なデバイスを使用しているため、メモアプリとしてはNotionを使っております。
今回は、そのNotionを使って読書録を作ってみようと考えました。
ちなみに、この記事もNotionで下書きを作成しております。

# アプリの実装方法について

## 大まかな処理の流れ

以下のようなフローで読書録を作成します。

1. 本のバーコードを読み取りISBNを取得
2. ISBNをキーに本の情報を取得
3. 取得した本の情報をNotionに送信

## どうアプリ化するか？

まず、**ウェブアプリ**として実装します。
理由としては、様々なデバイスで使用できることなどが挙げられます。
次に、ウェブアプリとして実装するにあたって、GAS(Google App Script)を活用します。
GASには、

- Googleアカウントさえあれば開発環境の整備が不要
- Google謹製であり情報が多く、安定している
- 無料である

などの多くの利点があるためです。

# 詳細な実装方法

## 1. GASでプロジェクトを作成
GASについてはネット上に情報が多く存在しておりますので、詳細は割愛いたします。
例えば、[この記事](https://www.acrovision.jp/service/gas/?p=339)が参考になると思います。
## 2. 「index.html」を作成
デフォルトで作成される「コード.js」に、以下のコードを書き込みます。
そして、同階層に「index.html」を作成します。
これにより、ウェブアプリとして公開した際に、「index.html」が表示されます。

```jsx
function doGet() {
  var template = 'index';
  return HtmlService
    .createTemplateFromFile('index')
    .evaluate()
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')  //html側にメタタグを設定しても意味がなかったためこちらで
}
```

## 3. quaggaJSの使用
バーコードを読み取ってISBNを取得するために、[quaggaJS](https://serratus.github.io/quaggaJS/v1.0.0-beta.1/examples/scan-to-input/)を使用します。
quaggaJSについては、[この記事](https://mam-mam.net/javascript/js_quagga.html)が参考になります。
今回は、2.で作成したindex.htmlにソースコードをそのまま追記させていただきました。
これで、バーコードを読み取ってISBNを取得する部分が完成しました。
いやはや、素晴らしいライブラリですね…！

:::note warn
quaggaJSですが、2017年以降メンテナンスされていないようです。
また、今回はGASでの実装なので関係ないのですが、localhost以外のサイトではhttpsでないと利用できません。（私はここで一度ハマりました…）
:::

([注意事項についての参考記事](https://nanbu.marune205.net/2021/12/barcode-scan-quaggajs.html?m=1))


## 4. ISBNから本の情報を取得

この目的を達成するためには、

- Google Books API
- 楽天ブックスAPI
- 国立国会図書館サーチAPI

など様々なAPIがありますが、今回は登録不要であることなどからGoogle Books APIを使用しました。
ただ、サムネイル画像が小さかったため、本の画像のみAmazonを使用しました。
今回は、

- タイトル
- サブタイトル
- 著者
- 出版日
- ページ数
- 画像

を取得することとし、まずは対応するinput要素をHTML部分に作成します。
（HTMLについても情報が多いため、詳細は割愛いたします。）
「index.html」のJavaScript部分におけるfunctionのサンプルコードは以下の通りです。
HTML部分のidに応じて改変してご利用ください。

```jsx
function GetInfo(){
    url = "https://www.googleapis.com/books/v1/volumes?q=isbn:"; //ISBNの手前まで
    isbn = document.getElementById("ISBN").value;

    let request = new XMLHttpRequest();
    request.open('GET', url+isbn);
    request.responseType = 'json';
    request.send();
    request.onload = function() {
        const result = request.response;
        document.getElementById("title").value = result["items"][0]["volumeInfo"]["title"];
        document.getElementById("subtitle").value = result["items"][0]["volumeInfo"]["subtitle"];
        document.getElementById("authors").value = result["items"][0]["volumeInfo"]["authors"];
        document.getElementById("publishedDate").value = result["items"][0]["volumeInfo"]["publishedDate"];
        document.getElementById("pageCount").value = result["items"][0]["volumeInfo"]["pageCount"];
        document.getElementById("imagePlace").src = "https://images-na.ssl-images-amazon.com/images/P/"+toISBN10(isbn)+".09.LZZZZZZZ.jpg";
        document.getElementById("imageSrc").value = "https://images-na.ssl-images-amazon.com/images/P/"+toISBN10(isbn)+".09.LZZZZZZZ.jpg";
      }
  }

const toISBN10 = (isbn13) => {
    // 1. 先頭３文字と末尾１文字を除く
    const src = isbn13.slice(3, 12);

    // 2. 先頭の桁から順に10、9、8…2を掛けて合計する
    const sum = src.split('').map(s => parseInt(s))
      .reduce((p, c, i) => (i === 1 ? p * 10 : p) + c * (10 - i));

    // 3. 合計を11で割った余りを11から引く（※引き算の結果が11の場合は0、10の時はアルファベットのXにする）
    const rem = 11 - sum % 11;
    const checkdigit = rem === 11 ? 0 : (rem === 10 ? 'X' : rem);

    // 1.の末尾に3.の値を添えて出来上がり
    return `${src}${checkdigit}`;
  }
```

:::note warn
AmazonはISBN10にしか対応していないため、ISBN13をISBN10に変換する必要があります。変換するコードは以下の記事から引用させていただきました。
:::

[JavaScriptで10桁のISBNを13桁に変換する](https://qiita.com/iz-j/items/27b9656ebed1a4516ee1)

## 5. Notion APIの下準備

Notion APIを使用して、取得した本の情報を送信します。
下準備として、データベースの作成およびIDを取得する必要があり、[この記事](https://note.com/wbrs/n/n10c15adcffc6)が参考になります。
データベースのフィールドとしては、上記で取得する項目の他に、「State」フィールドを追加しました。
後続の工程で、デフォルトでは「興味あり」というセレクト項目が送信されるようにします。

## 6. Notion APIを使って情報を送信

index.htmlから直接Notion APIに接続すると、CORSエラーが発生してしまいます。
そのため、間にGASを挟むことでCORSエラーを回避しました。
具体的には、「コード.gs」に以下のコードを追記し、「index.html」側のフォームからdoPostを呼び出すことでCORSエラーを回避しました。
なお、JSONの構造については[この記事](https://www.6666666.jp/productivity/20210617/)を参考にしました。

```jsx
function doPost(e){
  SendToNotion(e.parameter.title,e.parameter.subtitle,e.parameter.authors,e.parameter.publishedDate,
                  Number(e.parameter.pageCount),e.parameter.isbn,e.parameter.imageSrc);
}

// Notion API
function SendToNotion(title,subtitle,authors,publishedDate,pageCount,isbn,imageSrc) {
  const notion_key = 'あなたのNotionキー';
  const database_id = 'あなたのデータベースid';

  json_data = {
    'parent': {'database_id': database_id},
    'properties': {
      // ↓ここにプロパティをカンマで区切って記述していく
      'Title': {
        'title': [
          {
            'text': {
              'content': title,
            }
          }
        ]
      },
      'Subtitle': {
        'rich_text': [
          {
            'text': {
              'content': subtitle,
            }
          }
        ]
      },
      'Author': {
        'rich_text': [
          {
            'text': {
              'content': authors,
            }
          }
        ]
      },
      'State': {
        'select': {
          'name': '興味あり',
          'color': 'yellow'
        }
      },
      'PublishedDate': {
        'rich_text': [
          {
            'text': {
              'content': publishedDate, //本によって形式がバラバラなので文字列とする
            }
          }
        ]
      },
      'PageCount': {
        'number': pageCount,
      },
      'ISBN': {
        'rich_text': [
          {
            'text': {
              'content': isbn,
            }
          }
        ]
      }
      // ↑ここまでプロパティ
    },
    'children': [
      // ↓こっちは本文ブロック
      {
        'object': 'block',
        'type': 'image',
        "image": {
          "type": "external",
          "external": {
              "url": imageSrc
          }
        }
      }
      // ↑ここまで本文
    ]
  };

  UrlFetchApp.fetch( 'https://api.notion.com/v1/pages', {
    "method" : "post",
    "headers" : {
      'Content-Type' : 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + notion_key,
      'Notion-Version': '2021-05-13',
    },
    "payload" : JSON.stringify( json_data )
  });
}
```

## 7. 見栄えの調整(スキップ可)

このままだと「index.html」の見栄えが良くないのですが、CSSを編集するのは手間であるため、今回はBootstrapを用いてイケてるデザインにしました。

## 8. ウェブアプリとして公開

GASの画面の「デプロイ」ボタンを押し、「ウェブアプリとして公開」することで完成です！
なお、iOSの場合はウェブサイトをホーム画面に追加する機能がありますので、あたかもネイティブアプリかのような見栄えにすることができます。

# 最後に

初投稿ということで、至らぬところが多かったと思いますが、少しでもご参考になれば幸いでございます。
また、「アプリを作った」と言いつつ、先人が作った素晴らしいライブラリやAPIをフルに活用させていただいておりますので、感謝の念に堪えません。
最後に、私はインディーゲームを開発しております。無料でサクッと遊べますので、ぜひリンク先だけでもご覧いただけますと幸甚です。

https://nyoroko.itch.io/sniper-on-the-ice

↑最新作です。HTML5ゲームですので、PCでもタブレットでもスマホでもすぐに遊ぶことができます。

https://nyoroko.itch.io/space-journey

↑初回作です。ダウンロードが必要なPCゲームです。

https://nyoroko.itch.io/

↑私のitchのプロフィールです。コンセプトと過去の作品が載っております。