HOMIKUJI（本×おみくじ）～本のAPIで遊んでみた

## 『「技術書」の読書術』を読んで
**ISBNをランダムに生成してヒットした本を読む**という読書法があることと、**本の情報を取得できるAPI**があることを知った。

**さあ、遊びの時間だ！**

## :closed_book:HOMIKUJI:blue_book:
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">📚HOMIKUJI📚<br>本のおみくじです⛩<br>あなたが今日読むべき本がわかります！<br>が、ハズレ率が高い😂<br>当たったらラッキー🤞<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/linebot?src=hash&amp;ref_src=twsrc%5Etfw">#linebot</a> <a href="https://twitter.com/hashtag/%E8%AA%AD%E6%9B%B8?src=hash&amp;ref_src=twsrc%5Etfw">#読書</a> <a href="https://t.co/EWzqXeyyad">pic.twitter.com/EWzqXeyyad</a></p>&mdash; 新米SEちーろってぃ@新婚💍 (@chiiirotty) <a href="https://twitter.com/chiiirotty/status/1654851609061629953?ref_src=twsrc%5Etfw">May 6, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 使ったもの
- [openBD API](https://openbd.jp/)（書誌情報取得）
- LINE Messaging API
- Node-RED（Railwayでデプロイ）

## 実装
### Node-REDフロー図
Node-REDはロジックが複雑になればなるほど、ローコードの視覚的にわかりやすいメリットが失われていくけど、とりあえずLINE Botを動かすには便利だから使っちゃう:thumbsup:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/58d8a1ac-466a-fdfc-e48a-b00aedaae74d.png)

```JSON:JSON
[{"id":"91315d34cf324824","type":"tab","label":"フロー 1","disabled":false,"info":"","env":[]},{"id":"9afbcf716abb1c19","type":"http request","z":"91315d34cf324824","name":"openBD APIにリクエスト","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.openbd.jp/v1/get?isbn={{payload}}","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":270,"y":180,"wires":[["3c255a4604136f26"]]},{"id":"103bde5d8b1c6a0b","type":"function","z":"91315d34cf324824","name":"ISBNをランダムで生成(※翔泳社の本限定)","func":"function generateRandomNumber() {\n    return Math.floor(Math.random() * 9999) + 1;\n};\n\nfunction calcCheckDigit(isbn) {\n    const digits = isbn.split(\"\").map((digit) => parseInt(digit));\n    const sum = digits.reduce((acc, digit, index) => {\n        return acc + (index % 2 === 0 ? digit * 1 : digit * 3);\n    }, 0);\n    return (10 - (sum % 10)) % 10;\n};\n\nfunction generateIsbn() {\n    const randomNumberAsStr = generateRandomNumber()\n        .toString()\n        .padStart(4, \"0\");\n    const isbn = `97847981${randomNumberAsStr}`;\n    return `${isbn}${calcCheckDigit(isbn)}`;\n};\n\nmsg.payload = generateIsbn();\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":120,"wires":[["9afbcf716abb1c19"]]},{"id":"3c255a4604136f26","type":"switch","z":"91315d34cf324824","name":"本情報を取得できたかどうか判定","property":"payload[0]","propertyType":"jsonata","rules":[{"t":"null"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":540,"y":180,"wires":[["febbdb7b7c397598"],["a146a5bfcd9b4bf0"]]},{"id":"febbdb7b7c397598","type":"change","z":"91315d34cf324824","name":"本情報を取得できなかった","rules":[{"t":"set","p":"payload","pt":"msg","to":"ハズレ！残念！今日はスマホをいじって過ごそう！","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":120,"wires":[["0e00e6e8a89fa1ee"]]},{"id":"a146a5bfcd9b4bf0","type":"change","z":"91315d34cf324824","name":"本情報を取得できた","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[0].summary.title","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":240,"wires":[["43f73f7c25930fa6"]]},{"id":"e932759c5e2634b8","type":"Webhook","z":"91315d34cf324824","name":"","url":"/webhook","x":80,"y":360,"wires":[["2ef1373e0bc7a1c7"]]},{"id":"0e00e6e8a89fa1ee","type":"ReplyMessage","z":"91315d34cf324824","name":"","replyMessage":"","x":860,"y":240,"wires":[]},{"id":"43f73f7c25930fa6","type":"template","z":"91315d34cf324824","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"『{{payload}}』を読もう！最高の一日になるよ！","output":"str","x":660,"y":240,"wires":[["0e00e6e8a89fa1ee"]]},{"id":"2ef1373e0bc7a1c7","type":"switch","z":"91315d34cf324824","name":"「今日読むべき本は」と聞かれたかどうか判定","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"今日読むべき本は","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":340,"y":360,"wires":[["103bde5d8b1c6a0b"],["3a332d34744835b2"]]},{"id":"3a332d34744835b2","type":"change","z":"91315d34cf324824","name":"デフォルトのリプライ","rules":[{"t":"set","p":"payload","pt":"msg","to":"「今日読むべき本は？」と聞いてね！","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":660,"y":360,"wires":[["0e00e6e8a89fa1ee"]]}]
```

**ランダムに生成したISBN**を含んだリクエストを**openBD API**に投げています。
本の情報を取得できた場合には**本のタイトル**を、取得できなかった場合は「**ハズレ**」のメッセージをLINE Botに返します。

### ISBNの生成

<details><summary>ISBNをランダムで生成(※翔泳社の本限定)するコード</summary>

```JavaScript:JavaScript
function generateRandomNumber() {
    return Math.floor(Math.random() * 9999) + 1;
};

function calcCheckDigit(isbn) {
    const digits = isbn.split("").map((digit) => parseInt(digit));
    const sum = digits.reduce((acc, digit, index) => {
        return acc + (index % 2 === 0 ? digit * 1 : digit * 3);
    }, 0);
    return (10 - (sum % 10)) % 10;
};

function generateIsbn() {
    const randomNumberAsStr = generateRandomNumber()
        .toString()
        .padStart(4, "0");
    const isbn = `97847981${randomNumberAsStr}`;
    return `${isbn}${calcCheckDigit(isbn)}`;
};

msg.payload = generateIsbn();

return msg;
```
</details>

こちらの記事を参考にさせてもらいました！
[完全なくじ引き読書法を実践するためのWebページを作った](https://zenn.dev/takeaship/articles/isbn-book-gacha)

ISBNって
978(固定) - 4(日本) - [[出版社記号](http://www.natubunko.net/mame/publisher.html)] - 書名記号(出版社ごと) - **チェックデジット**
というルールで採番されてるの知ってた？私は知らなかった。

この**チェックデジット**の計算方法が、
**左から奇数桁の合計 + 左から偶数桁の合計 * 3 の下一桁を10から引いた値**
という実装するのがちょいめんどくさい計算方法だったので、↑の記事が大変助かりました:pray:

翔泳社の本限定（出版社記号7981）にしているのは、**ISBNを完全ランダムにすると全然ヒットしない**からです。↑に載せているツイートの動画を見てもらえばお分かりかと思いますが、翔泳社限定にしてもヒット率2割以下です:sweat_smile:
それだけ世の中にはたくさんの本があるということですね:book:

## 今後やりたいこと
[図書館API](https://calil.jp/doc/api_ref.html)と組み合わせたい！
ランダムに本をピックアップ
↓
現在地近くでその本が読める図書館を提示
なーんてできたらおもしろそう:relaxed: