Notionからkindleハイライト呼び出してみた!
概要
Notionからkindleハイライトを呼び出す方法を、以下の2段階で紹介します。
すぐ実装できるやり方(ただし、表示が見にくい)
表示が見やすい&応用性があるやり方(ただし、実装がちょっと手間)
利用イメージ
まず、Notionでこんな形でハイライトを表示するページへのリンクを持つプロパティを作り、このリンクをクリックすると
すぐ実装できるやり方だと以下のような表示
表示が見やすい&応用性があるやり方だと以下のような表示がされます
はじめに
知っている方も多いと思いますが、KindleのハイライトはWebブラウザからも確認できます。
https://read.amazon.co.jp/notebook
このページでは、ハイライトやメモに更新があった順に書籍が表示されるようになっています。
ところで、私は最近Notionで読書管理をしています。
本の内容を思い出すに際して、Notionから読書メモを見直すのですが、その時に、選択した本の「メモとハイライト」のページに飛べるようにしたいと考えました。
先述の通り「メモとハイライト」のページは、ハイライトやメモに更新があった順に書籍が表示されるため、更新日時が古いとスクロールか検索しなければならず探すのがちょっと手間になってしまいます。
なんとか直接狙った書籍の「メモとハイライト」に飛べないか方法を模索しました。
すぐ実装できるやり方
Amazonの商品はASINというIDで管理されており、Kindle書籍もそうです。書籍のASINがわかると、その書籍の商品ページや書籍を開くためのURLなどが自動的にわかります。
ASINについては、以下の記事でも解説しているので、良ければ読んでみてください。実装のためにまずは、以下の記事の「準備」の章の内容に沿って、ASINのプロパティを作成します。
「メモとハイライト」は一見すると、書籍を選択してもURLにASINは含まれていないように見えます。
しかし、書籍を選択したときの挙動を確認すると、以下のURLにアクセスしていることがわかりました。やはり、ASINを用いたURLでその書籍のハイライトの情報を持ってきているようです。
https://read.amazon.co.jp/notebook?asin={ASIN}&contentLimitState=&
このURLをブラウザで直接表示すると、
このように表示されます。ちょっと見づらいです。「メモとハイライト」のページでは、このhtmlを受け取ってJavaScriptで整形して見やすくしているようです。
とりあえず、見づらくはありますが、NotionにASINのプロパティを作ったうえで、以下のような数式プロパティを設定することで、このページにNotionから直接飛べるようになります。
link("ハイライト","https://read.amazon.co.jp/notebook?asin="+prop("ASIN")+"&contentLimitState=&")
表示が見やすい&応用性があるやり方
ただ、このページ単体だと、見づらい他にも問題があり、100個程度しかハイライトが読み込まれないです。
「メモとハイライト」のページでは、ちゃんとすべてのハイライトが表示されているのになぜだろうと調べてみると、ハイライト数が多い場合はこのページからさらに続きのページを読み込むアクセスが発生しているようでした。
HTMLを読むと次のページへのトークンなどが設定されており、それをURLパラメータに加えることで、ハイライトをすべて読み込んでいるようでした。
https://read.amazon.co.jp/notebook?asin={asin}&token={nextPageToken}&contentLimitState={contentLimitState}
この挙動を再現し、またハイライトを表形式で表示するJavascriptを書いてみました。
// URL からクエリパラメータを取得する
const queryParams = new URLSearchParams(window.location.search);
const asin = queryParams.get('asin');
// HTMLからハイライトの情報を取得
function getHighLight(doc){
// ハイライトの数を数える
let hl_len = doc.getElementsByClassName("kp-notebook-highlight").length;
// 各ハイライトの情報を抽出
let hl_array = [];
for (i = 0; i < hl_len; i++) {
let hl_dict = {}
hl_dict["text"] = doc.getElementsByClassName("kp-notebook-highlight")[i].firstChild.innerHTML;
hl_dict["num"] = doc.querySelectorAll('[id="kp-annotation-location"]')[i].value; // 本来idは重複するものではないから邪道
hl_dict["note"] = document.querySelectorAll('[id="note"]')[i].innerText; // 本来idは重複するものではないから邪道
hl_dict["color"] = doc.getElementsByClassName("kp-notebook-highlight")[i].classList[3].split("-")[3];
hl_array.push(hl_dict);
}
return hl_array
}
// テーブルに行を追加する関数
function populateTable(data) {
let table = document.getElementById('myTable').getElementsByTagName('tbody')[0];
data.forEach(rowData => {
let row = table.insertRow();
// 1列目
let cell0 = row.insertCell();
let num = rowData["num"];
let customUrlScheme = `kindle://book?action=open&asin=${asin}&location=${num}`;
let link = `<a href="${customUrlScheme}">${num}</a>`
cell0.innerHTML = link;
// 2列目
let cell1 = row.insertCell();
cell1.textContent = rowData["text"];
cell1.className = "text-content"; // 折り返し用のクラスを追加
// 3列目
let cell2 = row.insertCell();
cell2.textContent = rowData["note"];
cell2.className = "text-content"; // 折り返し用のクラスを追加
// 背景色を設定
cell0.style.backgroundColor = rowData["color"];
});
}
// 次のtokenとcontentLimitStateを抽出し、URLを生成
function getNexUrl(doc){
let contentLimitState = doc.getElementsByClassName("kp-notebook-content-limit-state")[0].value
let nextPageToken = doc.getElementsByClassName("kp-notebook-annotations-next-page-start")[0].value
if(nextPageToken!=''){
let nextUrl = `https://read.amazon.co.jp/notebook?asin=${asin}&token=${nextPageToken}&contentLimitState=${contentLimitState}`;
return nextUrl;
}else{
return null
}
}
// HTMLをテーブルで上書き
function rewriteHtml(hlArray){
let tblString = `
<div id="parent">
<table id="myTable">
<thead>
<tr>
<th>No.</th>
<th>Text</th>
<th>Memo</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
`
document.body.innerHTML = tblString
populateTable(hlArray)
}
// 次のページがあれば、それを取得していく
async function fetchSequentially(initialUrl,hlArray) {
try {
const parser = new DOMParser();
let currentUrl = initialUrl;
let count = 0;
while (currentUrl && count<20) {
const response = await fetch(currentUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let result = await response.text();
let doc = parser.parseFromString(result, 'text/html');
newHlArray = getHighLight(doc)
hlArray=hlArray.concat(newHlArray)
// 次のURLをresultから決定する。URLがない場合はループを終了する。
currentUrl = getNexUrl(doc);
count++;
}
rewriteHtml(hlArray)
} catch (error) {
console.error('Fetch error:', error);
}
}
if(asin){ // パラメータとしたasinが与えられている場合のみ実行(メモとハイライトのページでは実行されないようにする)
let hlArray= getHighLight(document);
const initialUrl = getNexUrl(document)
fetchSequentially(initialUrl,hlArray);
}
Chromeの開発者ツールのコンソールに、このコードをコピペすると結果を見ることができます。私はこれをChrome拡張にして、ハイライトのページを開いたら自動的にこれが実行されるようにしています。
このコードで重要な部分はgetHighLightとgetNexUrl, fetchSequentiallyです。
getHighLight:HTMLから、ハイライトの色、ページ番号、メモなどの情報を抽出
getNexUr:ハイライトがさらに存在する場合、次のHTMLへアクセスするためのトークンなどの情報を抽出しています
fetchSequentially:次のHTMLへアクセスし、同様にハイライトの情報を抽出
ハイライトの情報は配列に格納しているので、やろうと思えばCSV出力したり、Notion APIでNotionのデータベースに挿入したりといろいろ応用ができます。
今回のJavascriptを実行すると、こんな感じの見た目で返されます。また、ページ番号をクリックすると、Kindleアプリで該当ページを自動的に開くようになっています。
Chrome拡張にした場合のコードはこちらに上げているので、興味のある方は試してみてください。
自作Chrome拡張のインストール方法はこちらが参考になります。
この記事が気に入ったらサポートをしてみませんか?