プログラミング

紙もKindleも電子書籍も、Obsidianに一冊残らず並べる本棚プラグインを作りました(Obsidian Bookshelf)

紙・Kindle・Google Playに散らばった蔵書を、Obsidianの中の一つの本棚にまとめる自作プラグイン Obsidian Bookshelf の紹介です。type: book のノートを集めて書影付きギャラリーに描く仕組みから、認証情報をプラグインに渡さないKindle取り込み、Amazon商品ページの多段フォールバック、シリーズ名の自動推定まで、実際のコードを抜粋しながらまとめました。

目次

はじめに

みなさんこんにちは。
今回は、紙の本もKindleもその他の電子書籍も、ぜんぶObsidianの中の「ひとつの本棚」にまとめて並べられる自作プラグイン Obsidian Bookshelf を紹介します。

作ったきっかけは、自分の蔵書が散らばりすぎて把握できなくなったことです。
紙で買った本、Kindleで買った本、Google Playブックスで買った本……と、買う場所が増えるほど蔵書が分散して、「あのシリーズ、何巻まで持ってたっけ」と毎回ストアを開いて確認していました。
同じ本をうっかり2回買ったこともあります。

この記事では、できることの紹介だけでなく、散らばった蔵書をどうやって1か所に集めているのか、その裏側を実際のコードを抜粋しながら書いていきます。
中身はTypeScriptで書いたObsidianプラグインです。
コードのブロックは雰囲気だけ眺めて読み飛ばしても話が繋がるように、直後に必ず日本語で種明かしを入れるので、「使えればいい」という方も安心して読み進めてください。
逆に、自分でObsidianプラグインを作ってみたい方には、設計判断のところがそのまま参考になるはずです。

コード一式を使ってみたいという方は、コメントでお知らせいただければお渡しします。

まず完成形から:Obsidianが「自分専用の本棚アプリ」になる

細かい説明の前に、出来上がりを見てもらうのが早いかと思います。
コマンド(Open Book Gallery)かリボンの本アイコンでギャラリーを開くと、書影付きのカードがグリッドで一面に並びます。

本棚ギャラリー。買った場所を問わず、書影付きカードが一覧で並びます。

ここに並んでいる本は、紙・Kindle・Google Playがごちゃ混ぜです。
買った場所が違うだけで、自分にとっては全部「持っている本」ですから、一覧では一緒に眺めたい。
その上で、ステータス(読了・読書中・未読)やカテゴリ(小説・マンガなど)、買った場所で絞り込んだり、シリーズごとにまとめたりできます。

そして地味に大事なのが、1冊1冊がそのままObsidianのノートだということです。
カードをクリックすればノートが開いて、そこに感想やメモを書けます。
読んだ本が本棚に並ぶだけで終わらず、自分の知識ベースの一部になる。
これが、専用の読書管理アプリではなく「Obsidianプラグイン」として作った一番の理由です。

ひとことで言えば、「買った場所を問わず横断 × 書影付きの本棚 × 感想が自分のテキストとして手元に残る」を同時に満たすのが、このプラグインの狙いです。
世の中の読書管理の手段は、だいたいこのどれかが欠けていました。

設計の出発点:本棚に「独自データベース」を作らなかった

このプラグインで一番最初に決めた設計判断が、本のデータを独自形式で持たないことでした。
ここが分かると、後の話が全部腑に落ちると思います。

本棚に並ぶ1冊は、特別なデータベースのレコードではありません。
Frontmatter(ノートの先頭に付ける属性欄)に type: book と書いた、ただのMarkdownノートです。
プラグインは、Vault(メモ全体)の中から type: book のノートを集めてきて、本棚として描いているだけ。
実際、Vault全体から本を集める処理はこれだけです。

getAllBooks(): BookData[] {
    if (this.cache) return this.cache;

    const books: BookData[] = [];
    const files = this.app.vault.getMarkdownFiles();

    for (const file of files) {
        const cache = this.app.metadataCache.getFileCache(file);
        const fm = cache?.frontmatter;
        if (fm && fm.type === 'book') {
            const book = this.parseBookFromFrontmatter(file, fm);
            if (book) books.push(book);
        }
    }
    this.cache = books;
    return books;
}

やっていることは「全Markdownノートを見て、Frontmatterに type: book があるものだけ拾う」だけです(this.cache は2回目以降を速くするための一時保存で、これは後の章で触れます)。
独自DBを持たないと決めたことで、本棚の実体はただのノートの集まりになり、感想を書くのも、他のノートに [[リンク]] で繋ぐのも、Obsidianの普通の機能でそのままできます。

ノート1冊はこんな形をしています。
Amazon検索やKindle取り込みで作られるノートは、この形で自動生成されます。

---
カバー: "99_Extra/covers/タイトル_ASIN.jpg"
タイトル: "本のタイトル"
著者: "著者名"
シリーズ: "シリーズ名"
出版社: "出版社名"
カテゴリ: "小説"
ステータス: "読了"
ソース: "Kindle"
お気に入り: true
asin: "XXXXXXXXXX"
作成日: 2023-01-01
type: book
---

(ここに読書メモや感想を自由に書ける)

属性のキーは日本語が優先で、無ければ英語キーに自動でフォールバックします。
その対応表が、コード上では1か所に集約されています。

export const FIELD_MAPPING = {
    title: ['タイトル', 'title'],
    author: ['著者', 'author'],
    series: ['シリーズ', 'series'],
    status: ['ステータス', 'status'],
    source: ['ソース', 'source'],
    // …以下、出版社・カテゴリ・お気に入り・カバー・作成日・評価
} as const;

たとえば「タイトル」が無ければ title を、「著者」が無ければ author を見にいく、という対応です。
なぜこんな二段構えにしたかというと、手で書いた日本語のノートと、CSVや他ツールから取り込んだ英語キーのノートを、同じ本棚に混ぜて並べたかったからです。
取り込み元によってキーが英語になっても、同じ棚に同居できます。

散らばった蔵書を、どうやって1か所に集めるか

土台が分かったところで、本題です。
「1冊ずつ手で登録するの?」と思われそうですが、まさにそこが面倒なので、バラバラの蔵書をまとめて取り込む入り口を4つ用意しました。
まず全体像を示して、次の章から1つずつ裏側を掘り下げます。

「紙の本は取り込めないのでは」と思うかもしれませんが、紙の本こそAmazon検索でタイトルを引いて、ソースを『紙』にして登録します。
これでKindle・Google Play・紙が、同じ見た目で同じ棚に並びます。
買った場所は「ソース」という1つの属性で区別するだけで、一覧では一緒に眺められる、という形にしました。

取り込みの裏側①:Kindle — 認証情報をプラグインに渡さない

目玉のKindle一括取り込みからいきます。
ここには、このプラグインで一番こだわった設計判断が詰まっています。

普通に考えると、「プラグインがAmazonにログインして蔵書一覧を取ってくる」のが楽です。
でもそれをやると、自分のAmazonのパスワードや二段階認証、ログイン状態(Cookie)をプラグインに預けることになります。
自作の小さなプラグインに認証情報を持たせるのは、安全面でも作りの面でも筋が悪い。

そこで発想を逆にして、取得は「すでにブラウザに入っている自分のログイン状態」に任せ、プラグインは出てきたデータを読むだけにしました。
具体的な流れはこうです。

  • Amazonの「コンテンツと端末の管理」ページを、ログイン済みのブラウザで開く
  • プラグインのモーダルにある「スクリプトをコピー」ボタンを押す(取得スクリプトはプラグインに同梱されていて、クリップボードに入ります)
  • ブラウザの開発者ツール(F12)のコンソールに貼り付けて実行 → 蔵書一覧のJSONが自動ダウンロードされる
  • そのJSONをプラグインに読み込ませる

認証情報は、最初から最後までブラウザの中だけにあり、プラグインを一切通りません。
プラグイン側のコードにも「HTTPでAmazonを叩く処理」は存在しないので、構造的に認証情報を扱いようがない、という作りです。

スニペットが裏でやっていること

コンソールで動くスニペットは、Amazonの内部APIを正規の手順で叩いています。
まずページのHTMLから本人確認用のトークン(csrfToken。Webサイトが「この操作はちゃんとこのページから来たものだ」と確認するための合言葉のようなものです)を抜き出し、それを付けて蔵書一覧APIに問い合わせます。
一度に取れる件数には上限があるので、100件ずつページをめくりながら全件を集めます。
中心のループはこれです。

const BATCH = 100;
let startIndex = 0;
const all = [];

while (true) {
    const res = await fetch(AJAX, { /* csrfTokenと取得位置を付けてPOST */ });
    const json = await res.json();
    const items = json?.OwnershipData?.items ?? [];

    if (!items.length) break;
    all.push(...items);

    if (items.length < BATCH) break;
    startIndex += BATCH;

    // Amazon への負荷配慮
    await new Promise((r) => setTimeout(r, 600 + Math.random() * 600));
}

ポイントは最後の1行です。
機械的にノンストップで叩くとサーバーに負荷をかけますし、不審なアクセスとして弾かれやすくもなります。
そこで各ページの取得のあいだに0.6〜1.2秒のランダムな間を入れています。
きっちり0.6秒ずつだと逆に機械的なので、あえて揺らしているわけですね。
集め終わったら全件を1つのJSONにまとめてダウンロードして終わりです。

読み込み時にやっている自動整理

ダウンロードしたJSONをプラグインに読み込ませると、ただ右から左に流すのではなく、いくつか自動で整えます。
なかでも効いているのがマンガと小説の自動振り分けです。
Kindleのデータには本の表示形式の情報が入っていて、「固定レイアウト(FIXED_LAYOUT)」ならマンガ/コミック、そうでなければ小説、と判定しています。
あわせて、同じASIN(Amazonの商品ID)が重複していたら1冊にまとめます。

// 同じ ASIN は1冊にまとめる(重複除外)
if (seen.has(book.asin)) continue;
seen.add(book.asin);

// 固定レイアウトならマンガ/コミック、それ以外は小説
const category = capabilities.includes('FIXED_LAYOUT') ? 'マンガ' : '小説';

読み込んだ直後に「○○件検出(マンガ N / 小説 M)」と内訳が出るので、取り込み前に中身を確認できます。
シリーズ名の推定(後述)もこの段で済ませます。

Kindle取り込みモーダル。読み込むとマンガ/小説の内訳が表示され、取り込み開始前に確認できます。

差分同期(増えた本だけ追加)

一度取り込んだあと、本を買い足したらどうするか。
毎回まっさらから作り直すのは無駄ですし、手で書いた感想を上書きで消してしまっては困ります。
そこで「新規のみ取り込む」というトグルを用意しました(初期値オン)。
オンにすると、すでに本棚にある本(同じASINのノートがあるもの)は飛ばして、増えた分だけ追加します。
既存の本に対しては「スキップ(安全)」と「上書き(更新)」も選べるので、感想を書いたノートを壊さずに蔵書だけ最新に保てます。
前回の取得日時も記録されるので、ときどき回すだけで本棚が育っていきます。

取り込みの裏側②:Amazon検索 — 構造がバラバラな商品ページを多段で攻略

1冊だけ足したいときや、紙の本を登録したいときは、Amazon検索が便利です。
タイトルで検索するか、商品ページのURL(やASIN)を指定すると、書影・著者・出版社・ページ数・発売日・価格・あらすじ・マンガかどうかまでを取得して、Frontmatter付きのノートを自動で作ります。

仕組みは、AmazonのHTMLを取得して必要な部分を抜き出す「スクレイピング」です。
ここで効いているのが、Obsidianが用意している requestUrl という通信機能です。
ブラウザのJavaScriptから他サイトを直接読もうとすると、CORSという仕組み(自サイト以外への勝手なアクセスをブラウザがブロックする安全機構)に阻まれるのですが、requestUrl を通すとこの制限を回避してHTMLを取得できます。

問題はここからで、Amazonの商品ページは本によって構造がまちまちです。
たとえば著者ひとつとっても、専用のリンクに入っている本、著者欄に入っている本、本文中のリンクにしか無い本、とバラバラ。
そこで、1つの項目につき「置き場所の候補」を上から順に試す多段フォールバックにしました。
著者の取得はこんな具合です。

// 著者(3段フォールバック)
let authors: string | null = null;

const contributorLink = doc.querySelector('a.contributorNameID');
if (contributorLink) {
    authors = contributorLink.textContent?.trim() || null;
}
if (!authors) {                       // ①で取れなければ著者欄を探す
    const byline = doc.querySelector('#bylineInfo');
    // …「著者ページ」「検索」リンクを除外しつつ最初の名前を採用
}
if (!authors) {                       // ②でも取れなければ別の著者要素
    const authorSpan = doc.querySelector('span.author');
    // …
}
if (!authors) authors = '不明';        // 最後まで取れなければ「不明」

候補を順に試して、最初に見つかったものを採用する。
どれも空振りなら「不明」で確定させて、処理は止めない。
出版社・ページ数・発売日も同じ思想で、まず箇条書き形式の詳細欄を見て、無ければ表形式の詳細欄を見る二段構えにしています。
スクレイピングは「相手の構造が変わるかもしれない」前提で書くのがコツで、1か所が外れても全体が倒れないようにこういう作りにしています。

書影は高画質のものを選び、URLに含まれるサイズ指定を大きいもの(_AC_UL320__AC_UL500_)に書き換えてから取得します。
マンガかどうかは、ページ上部のパンくず(カテゴリの並び)に「コミック」「マンガ」が含まれるかで判定しています。

ブロックされないための配慮

短時間に機械的なアクセスを繰り返すと、Amazon側で弾かれます。
そこで、アクセスのたびにブラウザの名乗り(User-Agent)を4種類からランダムに切り替え、1〜3秒のランダムな間を空けてから取得しています。
それでも大量に処理すると弾かれることはあるので、CSVでのまとめ検索は急がず回す前提の作りです。

取り込みの裏側③:Google Play と CSV — 泥臭い前処理

残り2つの入り口は、いずれも「きれいなデータが手に入らない」ことへの対処がほとんどです。

Google Playには、Kindleのようなきれいな蔵書APIが見当たりませんでした。
そこで、ライブラリページで専用のスニペットを実行し、画面に並んでいる本のタイトルを拾ってCSVにします。
ただ、Playブックスの画面はタイトルを aria-label(読み上げソフト用のラベル)として持っているのですが、同じ属性を「メインメニュー」「並べ替え」などのUIラベルや、「マンガ、7冊」のようなカテゴリ件数表示にも使っているため、素直に拾うとゴミが大量に混ざります。
そこを除外するために、まず「捨てるものの一覧」を2種類用意しています。

// 明らかに書籍タイトルでない固定UIラベル
const NOISE_EXACT = new Set([
    'メインメニュー', '検索', '並べ替え', 'フィルタ', /* …他 */
]);
// 「マンガ、7 冊」のようなカテゴリ件数表示
const CATEGORY_COUNT = /、\s*\d+\s*冊\s*$/;

const set = new Set();
document.querySelectorAll('[aria-label]').forEach((el) => {
    const raw = el.getAttribute('aria-label');
    if (!raw) return;
    const label = raw.trim();
    if (label.length < 3 || label.length > 200) return;
    if (NOISE_EXACT.has(label)) return;
    if (CATEGORY_COUNT.test(label)) return;
    set.add(label);
});

NOISE_EXACT は完全一致で弾く固定UIラベルの集合、CATEGORY_COUNT は「、7 冊」で終わる文字列にあたる正規表現です。
画面上の aria-label を持つ要素を総なめして、短すぎる・長すぎるラベルと除外リストに当たったものを落とし、残ったものを書籍タイトルの候補として拾い上げる、という流れになっています。

スニペットが出力するのはタイトルだけのCSVです。
これをプラグインの「CSV一括検索」に読み込ませると、1件ずつAmazon検索にかけて、書影・著者などの情報を補完しながらノートを作ります。
タイトルしか取れない入り口でも、Amazon検索と連携させることで、他の入り口と同じ情報量で本棚に合流できるわけです。

もうひとつの入り口のCSV取り込みは、表計算ソフトなどで作ってある既存の蔵書リストをそのまま活かすためのものです。
CSVのヘッダー行から列を自動で判別し(日本語・英語どちらの列名にも対応)、文字コードはUTF-8とShift-JISの両方を受け付けます。
UTF-8のBOM(ファイル先頭にこっそり付く目印用の数バイト)も自動で検出して処理を切り替えるので、Windowsの表計算アプリが出力したCSVでもそのまま読めます。
こちらもタイトルだけの行はAmazon検索で補完できるので、「書名を並べただけのリスト」からでも本棚が作れます。

シリーズを「勝手にまとめる」仕組み

マンガのように巻数の多い本は、1冊ずつカードを並べると本棚があふれて見づらくなります。
そこで、グループ化で「シリーズ」を選ぶと、同じシリーズの本が1枚のカードに重なって表示されるようにしました。
これが「シリーズスタック」です。
カードをクリックするとそのシリーズの巻が展開され、ドラッグで並べ替えると、その並び順はカスタム順序として保存されます。

シリーズスタック。同シリーズが1枚に重なります。例えば「名探偵コナン」をクリックすると一覧が展開されます。

このまとまりの肝が、タイトルからシリーズ名を推定する処理です。
「シリーズ」欄を手で埋めなくても、ある程度は自動でまとまってほしい。
考え方は「タイトルの末尾から『巻数っぽいもの』を剥がして、残りをシリーズ名とみなす」というものです。

// 末尾の巻数マーカーを順に試して剥がす
const volumePatterns: RegExp[] = [
    /[\s ]*第\s*[\d0-9]+\s*巻\s*$/,      // 「第3巻」
    /[\s ]*[\d0-9]+\s*巻\s*$/,           // 「3巻」
    /[\s ]*[((]\s*[\d0-9]+\s*[))]\s*$/, // 「(3)」
    /[\s ]*[Vv][Oo][Ll]\.?\s*[\d0-9]+\s*$/, // 「Vol.3」
    /[\s ]*[①-⑳]\s*$/,                    // 丸数字「③」
    /[\s ]*[\d0-9]+\s*$/,                // 末尾の数字
];
for (const re of volumePatterns) {
    if (re.test(s)) {
        return s.replace(re, '').trim() || null;  // 剥がした残りがシリーズ名
    }
}
return null;  // 巻数マーカーが無ければ「単巻」扱い

「第N巻」「N巻」「(N)」「Vol.N」、丸数字、末尾の数字……と、巻数の書き方の候補を上から順に試して、当たったらそれを取り除いた残りをシリーズ名にします。
どれも当たらなければ単巻とみなしてシリーズなし。
実際にはこの前段で、末尾の出版社ラベル(数字でない丸カッコ書き)を先に剥がす処理も入れています。
「ある作品 3巻」なら「ある作品」、「ある作品 Vol.2」なら「ある作品」がシリーズ名になる、という具合です。

機械的な推定なので外すこともあります。
そのときは、これも結局ただのノートなので、「シリーズ」欄に手で正しい名前を入れれば確実にスタックにまとまります。
なお小説のタイトルでこれをやると誤検出が多いので、自動推定はマンガに絞っています。

本棚を軽く・崩れなく保つ工夫

最後に、ふだん使っていて気持ちよく感じる部分の裏側です。

編集したらすぐ反映、でも重くしない。
本のノートを手で書き換える(カテゴリを直す、感想を足す)と、Obsidianが「このノートが変わった」と教えてくれます。
それを受けて本棚を描き直すのですが、入力のたびに描き直すと重い。
そこで、最後の編集から300ミリ秒待ってからまとめて描き直す「デバウンス」を入れています。

let debounceTimer: ReturnType;
this.registerEvent(
    this.app.metadataCache.on('changed', () => {
        this.bookRepository.invalidateCache();   // 蔵書キャッシュを捨てる
        clearTimeout(debounceTimer);             // 直前の予約をキャンセル
        debounceTimer = setTimeout(() => this.refresh(), 300);  // 300ms後にまとめて再描画
    })
);

clearTimeout で「さっき予約した再描画」を毎回キャンセルしているのがポイントで、編集が連続しているあいだは描き直さず、手が止まって300ミリ秒経ってから一度だけ描く、という挙動になります。
さらに、蔵書は一度読んだらメモリにキャッシュしておく(編集があったときだけ捨てて読み直す)ので、ふだんの表示は軽いままです。
前のほうで出てきた if (this.cache) return this.cache; がこれですね。

書影は手元に保存する。
取得した書影は、リモートのURLを参照しっぱなしにせず、Vault内のフォルダ(既定は 99_Extra/covers)にダウンロードして保存し、ノートからはそのパスを参照します。
こうしておけば、オフラインでも、元のサイトから画像が消えても、本棚が崩れません。
画像URLが分からない古い本のために、ASINから書影URLを組み立てるフォールバックも用意しています。

// productImage が無い古い書籍向けに、ASIN から書影URLを組み立てる
static buildAmazonCoverUrl(asin: string): string | null {
    if (!asin) return null;
    return `https://m.media-amazon.com/images/P/${asin}.09.LZZZZZZZ.jpg`;
}

どう作ったのか(技術構成のまとめ)

ここまでの仕組みを支える土台を整理しておきます。
自分でObsidianプラグインを作ってみたい方の参考になればと思います。

設計は、役割ごとにモジュールを分けています。

Plugin (main.ts)
├── BookRepository       Vaultから type:book のノートを集める(メモリキャッシュ)
├── CoverService         書影のダウンロード・パス解決
├── BookNoteManager      書籍ノートの作成・更新
├── AmazonSearchService  Amazon.co.jp のスクレイピング
└── BookGalleryView      本棚UIの司令塔(状態管理・永続化)
    ├── ControlBar       検索/フィルタ/ソート/グループのUI
    └── BookGrid         グリッド描画(フラット/グループ/シリーズスタック)

データの流れも一本道です。
BookRepository がVaultから type: book のノートを集めてメモリに持ち、BookGalleryView がそれを「絞り込み → 2段ソート → グリッド描画」に通して表示する。
取り込み系(Kindle・Amazon・CSV)は、入り口は違っても最終的に同じFrontmatter付きノートを作るところに合流するので、いったんノートになれば後はすべて共通の本棚ロジックに乗ります。

モバイルにも対応しています。
本棚のグリッドは列数を固定せず、カードの最小幅に合わせて画面幅いっぱいに自動で折り返す作り(CSSの auto-fill)にしていて、スマホではカードの最小幅を小さく(PCの150px相当→100px相当に)切り替えて、狭い画面でも詰めて並ぶようにしています。
地味に苦労したのがAndroidの「戻る」操作で、シリーズ詳細を開いている状態で戻るボタンを押したら本棚一覧に戻ってほしい。
Obsidianのキー入力の仕組み(Scope)を使って、シリーズ詳細を開くときに Escape(=Androidの戻る)を一時的に横取りし、本棚に戻すようにしています。

導入と設定

導入は、プラグインフォルダをVaultの .obsidian/plugins/ に置いて、Obsidianの設定から有効化するだけです。
設定項目もシンプルで、基本は2つのフォルダを決めるだけです。

フィルタ・ソート・グループの状態は自動で保存されるので、毎回設定し直す必要はありません。
次に開いたとき、前回の見え方がそのまま再現されます。

使い方チュートリアル

シナリオA:Kindleの蔵書を丸ごと本棚にする

  • Amazonの「コンテンツと端末の管理」ページを開く
  • プラグインの「Kindle蔵書を取得」を開き、「スクリプトをコピー」→ ブラウザのコンソールに貼り付けて実行 → JSONがダウンロードされる
  • そのJSONをモーダルで読み込む(マンガ/小説の内訳が表示されます)
  • 「新規のみ取り込む」をオンにして「取り込み開始」

これでKindleの全蔵書が一気に本棚に並びます。
マンガはシリーズスタックでまとまるので、巻数が多くてもすっきり見えます。
2回目以降は、同じ手順で増えた本だけが追加されます。

シナリオB:紙の本を1冊足して、感想を書く

  • 「Amazon書籍検索」でタイトルを検索し、目的の本を選ぶ
  • できたノートのソースを「紙」にする
  • そのノートの本文に、読んだ感想やメモを書く
  • 関連する別の本やテーマのメモに [[リンク]] でつなぐ

これで紙の本もKindleの本と同じ棚に並び、しかも感想が自分の知識ベースの一部になります。

よくあるトラブルとQ&A

Q. Kindleの取り込みで「JSONの形式が認識できません」と出る

A. 付属のスニペットで生成したファイル以外(手元の別のJSONなど)を読み込んでいる可能性があります。
「コンテンツと端末の管理」ページでスニペットを実行して出力されたファイルを指定してください。

Q. スニペットを実行しても「csrfTokenが見つかりません」と出る

A. 実行するページが違う可能性が高いです。
Amazonの「コンテンツと端末の管理(蔵書一覧)」ページの上で実行してください。
別のページだと本人確認用のトークンが取れません。

Q. Amazon検索で結果が出ない/書影が取れない

A. 短時間に何度も検索すると、Amazon側で一時的にブロックされることがあります。
アクセス間隔は空けていますが、うまくいかないときは少し時間を置いてください。
古い本で書影が出ないときは、ASINから書影を引くフォールバックが働きます。

Q. Google Playのタイトルが取れない/一部しか取れない

A. ライブラリページを一番下まで一度スクロールして、全部の本を画面に読み込ませてから実行してください。
遅延読み込みのため、スクロールしないと画面に載っていない本は拾えません。

Q. マンガなのに小説に分類される(またはその逆)

A. 自動判定は完璧ではありません。
書籍ノートの「カテゴリ」を手で書き換えれば、本棚にもすぐ反映されます(編集すると自動で再描画されます)。

Q. シリーズがうまくまとまらない

A. シリーズ名はタイトルからの推定なので、タイトルの付け方によっては外れます。
ノートの「シリーズ」欄に明示的に名前を入れれば、確実にスタックにまとまります。

Q. スマホでも使える?

A. 使えます。
本棚は画面幅に合わせて列数が自動で決まり、スマホではカードを小さめにして並びます。
Androidの戻るボタンにも対応しています。
Obsidianを同期していれば、PCで取り込んだ蔵書をそのままスマホの本棚で眺められます。

まとめ

いかがでしたか。
今回は、紙・Kindle・その他の電子書籍をObsidianの中の一つの本棚にまとめる、自作の読書管理プラグインを紹介しました。
中身を振り返ると、

  • 1冊が type: book のただのMarkdownノートなので、蔵書も感想も自分の手元に残り、[[リンク]]で知識ベースとつながる
  • Kindle・Google Play・Amazon検索・CSVの4つの入り口で、バラバラの蔵書をまとめて取り込める
  • Kindle取り込みは認証情報をプラグインに渡さず、ブラウザのセッションで完結させた
  • 構造の違うAmazonの商品ページは多段フォールバックで攻略し、巻数の多いマンガはシリーズ推定+スタックですっきりまとめる

という設計でした。
蔵書がひとつの本棚に集まると、「自分はこういう本を持っていて、こう読んできたんだな」が一目で見えて、想像以上に気持ちがよかったです。
同じ本をうっかり2回買うこともなくなりました。

このプラグインは自分用に作ったものですが、同じように蔵書の分散で困っている方には役立つかもしれません。
コード一式を使ってみたいという方は、コメントでお知らせいただければお渡しします。

この記事が誰かの役に立てばうれしいです。