メインコンテンツへスキップ
見出し画像

Amazonで本の情報をRoam Researchにコピーするブックマークレットを作った

    去年の秋頃からRoam Researchを使い始めて、すべてのメモやタスク管理などを集約して管理するようにしていますが、リアルな本のメタデータを管理することが実現できておらず、どうにかしたいなぁと思っていたところたまたまこちらの記事を見て、OpenBDから書籍のメタデータを取得してRoamにコピペできるようにしたらめっちゃ便利だと思い作りました。

    アマゾンのページから書籍情報をコピーするBookmarkletを作りました。
    大部分@serizawa_jpさんのBookmarkletを参考にさせてもらいました。

    Bookmarkletは検索エンジンに登録すると、キーボードから手を離さずにコピペできるので快適です。#Roam部 https://t.co/9nid6cgaNU pic.twitter.com/HAT4GXUZnR

    — UD (@udus122) April 17, 2021


    使い方

    javascript:void((function(){var s = document.createElement("script");s.type = "text/javascript";s.src = "https://roam-kit.netlify.app/amazon2roam.js";document.getElementsByTagName("head")[0].appendChild(s);})());

    1. 上記のコードをBookmarkに登録する
    2. 任意のAmazonの書籍ページでBookmarkをクリックする
    3. Roamにクリップボードの内容をペーストする

    以上です。

    仕組み

    1. アマゾンの書籍ページには、以下の形式でcanonicalURLが設定されているので、そこから書籍のISBNを抽出しています。

    <link rel="canonical" href="https://www.amazon.co.jp/title/dp/ISBN">

    2. ISBNをもとにしてfetchAPIでOpenBDへリクエストを投げて、必要なデータ(目次や著者情報、書籍紹介など)を取得してきて、Roam形式に整形してクリップボードへコピーしているという流れになっています。

    取得してきたデータの整形の部分はserizawa-jpのBookmarkletのコードをめちゃくちゃ参考にさせてもらっています。

    余談

    ちなみに、デモのGifではBookmarkをクリックするのではなく、検索エンジンからBookmarkletを呼び出しています。
    そうすることの何がメリットかと言うと、キーボードから手を離さずにコピペの作業をすることがないので数秒ほどの時間と手を動かすエネルギーを節約することができます。(あと、余計なこと考えなくて済むので思考も途切れない気もます。)

    以下で検索エンジンにBookmarkletを登録するやり方を紹介します。
    (Chromeを前提としていますので、他のブラウザだとできない可能性があります。)

    1. `ctrl + ,`で設定画面を開きます。
    2. 左のサイドバーにある"検索エンジン"をクリックします。
    3. すると、以下のような画面になるので、"追加"ボタンをクリックします

    画像1

    4. 検索エンジンの部分に表示名、キーワードの部分にトリガーとなる文字列、URLの部分に`javascript:`から始まるBookmarkletを登録すると、検索エンジンからBookmarkletを実行することができるようになります。

    画像2

    上記の例だと、アドレスバーに`amazon2roam`と入力してEnterを押すと、Bookmarkletが実行されます。

    コード

    念の為、今回のBookmarkletの作成で使用しているBookmarkletのコードをすべて載せておきます。
    ただ、TypeScriptで書いているので、このコードをブラウザのConsoleにコピペしても実行できないと思います。

    const copyToClipboard = (text: string) => {
     const dummy = document.createElement("textarea");
     document.body.appendChild(dummy);
     dummy.value = text;
     dummy.select();
     document.execCommand("copy");
     document.body.removeChild(dummy);
    };
    
    const putIndent = (s: string, n: number = 2) => {
     return `${" ".repeat(n)}${s}`;
    };
    
    const roamfy = (d: Date) => {
     const ord = (n: number) =>
       n +
       (n > 0
         ? ["th", "st", "nd", "rd"][(n > 3 && n < 21) || n % 10 > 3 ? 0 : n % 10]
         : "");
     const year = d.getFullYear();
     const month = d.toLocaleString("en-us", { month: "long" });
     const date = ord(d.getDate());
     return `${month} ${date}, ${year}`;
    };
    
    (async () => {
     const toDate = (YYYYMMDD: string) => {
       const year = Number(YYYYMMDD.substr(0, 4));
       const month = Number(YYYYMMDD.substr(4, 2));
       const day = Number(YYYYMMDD.substr(6, 2));
       return new Date(year, month, day);
     };
    
     const canonicalUrl =
       document.querySelector<HTMLLinkElement>('link[rel="canonical"]')?.href ??
       "";
     const asin = canonicalUrl.split("/").pop();
     const response = await fetch("https://api.openbd.jp/v1/get?isbn=" + asin);
     const [data] = await response.json();
     if (!data) {
       alert("版元ドットコムにデータがありませんでした。");
       return;
     }
     const {
       onix: {
         CollateralDetail: { TextContent: bookInfoList },
         DescriptiveDetail: {
           TitleDetail: {
             TitleElement: {
               TitleText: { content: title = "" } = { content: "" },
               Subtitle: { content: subtitle = "" } = { content: "" },
             },
           },
           Contributor: contributors = [],
         },
         PublishingDetail: {
           Publisher: { PublisherName: publisher = "" } = { PublisherName: "" },
           PublishingDate: [{ Date: publishingDate = "" } = { Date: "" }],
         },
       },
     } = data;
    
     const isToc = (m: { TextType: string }) => m.TextType === "04";
     const toc = bookInfoList.find(isToc)?.Text.split("\n") ?? [];
     const bookDescriptions =
       bookInfoList
         ?.filter((m: any) => !isToc(m))
         ?.map((m: { Text: string }) => m.Text) ?? [];
    
     const authorsInfo = contributors.map(
       (m: { PersonName: { content: string }; BiographicalNote: string }) => ({
         name: m.PersonName.content ?? "",
         bio: m.BiographicalNote?.replace("\n", " ") ?? "",
       })
     );
    
     const res = [
       `[[Book/${title} ${subtitle}]]`,
       putIndent(`Source:: ${canonicalUrl}`, 2),
       putIndent(`ISBN:: ${asin}`, 2),
       putIndent(`Authors::`, 2),
       ...authorsInfo.map((m: { name: string; bio: string }) =>
         [putIndent(`[[${m.name}]]`, 4), putIndent(m.bio, 6)].join("\n")
       ),
       putIndent(`Publisher:: ${publisher}`, 2),
       putIndent(`Published:: [[${roamfy(toDate(publishingDate))}]]`, 2),
       putIndent(`Accessed:: [[${roamfy(new Date())}]]`, 2),
       putIndent(`Tags:: #Book  `, 2),
       putIndent(`Notes:: `, 2),
       putIndent(`Related:: `, 2),
       putIndent(`Toc::`, 2),
       ...toc.map((m: string) => putIndent(m, 4)),
       putIndent("Descriptions::", 2),
       ...bookDescriptions.map((m: string) => [putIndent(m, 4)]),
     ].join("\n");
    
     copyToClipboard(res);
    })();

    もし興味ある方は自分のGitHubに他にもBookmarkletを載せているので御覧ください。(といっても、serizawaさんのものを自分用にフォーマットを変えて、TypeScriptとして管理しているだけですが。。。)

    他にもBookmarkletをGitHub✕Netlifyで管理すると、色々捗ることなど書きたいことはありますが、それはまた別の記事にします。

    参考


     
     

    UD

     
     
    DataOps Engineer | データ基盤のコスト最適化・運用自動化 2.5PB/1日1000万クエリ規模の基盤で年間1.4億円のコスト削減を実現

    あなたへのおすすめ