- 先に成果物
- 初期案
- DOM から ASIN を読み取る編
- manifest v3 では service_worker は DOM にアクセスできない
- 問題点を整理
- 本題
- 全体
- まとめ
- 得たもの
- 参考ドキュメント
ブックマークレット版はすでに紹介しましたが、もともとは Chrome 拡張として作りたいと思っていました。
とりあえず動くものはできたので紹介します。
先に成果物
初期案
動かし方のイメージとしては、疑惑のリンクを右クリックしたときにコンテキストメニュー(右クリックで出てくるメニュー)の中にその「綺麗なURLを別タブで開く」ボタンが選択できる形式です。
なので、選択されたURLに対して GET メソッドの HTTP リクエストをしないといけないわけですが、使ったのが fetch API です。
こんな感じで、 res オブジェクトの url 属性には最終的なリダイレクト先のURLが入ってきます。
fetch(srcUrl).then(res => { if (!res.ok) { return Promise.reject(new Error(`${res.status}: ${res.statusText}`)); } else { return res.url; } });
ちなみに Promise とか async / await のあたりを全く知らなかったので一から勉強しました。
このページがおすすめです。
話を戻しまして、これで取得したURLを加工して余計な文字列を排除すればOKということで作ってみたのですが、URLの加工のパターンを考慮漏れがない形で実装するのが面倒だと思いました。
例えばこちらですが、
URLをそのままコピペすると
https://www.amazon.co.jp/%E8%B3%83%E5%8A%B4%E5%83%8D%E3%81%A8%E8%B3%87%E6%9C%AC-%E5%B2%A9%E6%B3%A2%E6%96%87%E5%BA%AB-%E3%82%AB%E3%83%BC%E3%83%AB-%E3%83%9E%E3%83%AB%E3%82%AF%E3%82%B9/dp/400341246X/?_encoding=UTF8&pd_rd_w=umEiV&content-id=amzn1.sym.1a81fc55-56fb-47c1-b953-191894c78d90&pf_rd_p=1a81fc55-56fb-47c1-b953-191894c78d90&pf_rd_r=ZGNPH0H3JVV4WQPXT1PS&pd_rd_wg=w7ctw&pd_rd_r=d4bb6a84-0fcb-488a-998d-16b42e401c84&ref_=pd_gw_ci_mcx_mi
のようになります。これを省略したいとします。
まずクエリストリングは全部除去できます。(なおかつ ?
より後ろを無条件にカットするだけなので簡単)
https://www.amazon.co.jp/%E8%B3%83%E5%8A%B4%E5%83%8D%E3%81%A8%E8%B3%87%E6%9C%AC-%E5%B2%A9%E6%B3%A2%E6%96%87%E5%BA%AB-%E3%82%AB%E3%83%BC%E3%83%AB-%E3%83%9E%E3%83%AB%E3%82%AF%E3%82%B9/dp/400341246X
その上で日本語の商品名がURLエンコーディングされたこの文字列を削除します。
%E8%B3%83%E5%8A%B4%E5%83%8D%E3%81%A8%E8%B3%87%E6%9C%AC-%E5%B2%A9%E6%B3%A2%E6%96%87%E5%BA%AB-%E3%82%AB%E3%83%BC%E3%83%AB-%E3%83%9E%E3%83%AB%E3%82%AF%E3%82%B9
最終的には以下が求まります。
https://www.amazon.co.jp/dp/400341246X
この dp
の後ろの文字列は、ブックマークレット編 でも述べた通り ASIN と呼ばれる商品コードで、各ページに記載されているものになります。
ただ、毎回このロジックで取れるわけではなく例外もあります。そしてその例外がどれぐらいあのかもよくわかっていない。
DOM から ASIN を読み取る編
ググったところ、以下のように id = 'ASIN' の要素を見れば値が取れるようです。
let asin = document.getElementById('ASIN').value
これを開いたページ内で実行するのが、前回のブックマークレット編でした。
が、自分はブックマークバーを表示しない派(画面を広くしたい)であり、 Chrome 拡張としてのアウトプットもほしいので別の方法を考えます。
manifest v3 では service_worker は DOM にアクセスできない
一番苦労したのはここです。
Chrome 拡張では、 manifest.json というファイルに「どのスクリプトを実行するのか」や「どんなアクションをこの拡張に許可するのか」を定義します。 で、この manifest.json ですが、今は V3 が最新になっています。
この V3 ですが、 V2 と比べて色々変更されたらしく、ググって出てきた V2 時代のやり方を適用しようにも根本的に適用できないパターンがありました。 それがこの service_worker では DOM にアクセスできないという話です。
なので、コンテキストメニューでクリックされたときにページのDOMを読み込んで、先ほどの ASIN を取得して…というのができないです。
問題点を整理
今の問題点を一旦整理します。
- ASIN をURLから確実に取得する方法がわからない
- A-1. 該当ページのDOMが取得できれば、そこから ASIN を取得できるので確実
- A-2. 正規表現で頑張る
- Chrome 拡張の service_worker では DOM を取得できない → そのままでは
A-1.
が使えない
そして A-1.
単体が前回のブックマークレットというわけですね。
本題
いくつか方法はありそうでしたが、 B-1.
の外部のスクリプトで実行するようにしました。
こんな感じです。
- backgroud.js
// 外部スクリプトを実行 const res = await chrome.scripting.executeScript({ target: {tabId: tab.id}, files: ['./content.js'], }); // 外部スクリプトからの戻り値 const url = res[0].result; if (url) { chrome.tabs.update({url: url}); };
- content.js
// DOM にアクセスする (() => { try { const baseUrl = 'https://www.amazon.co.jp/dp/'; const asin = document.getElementById('ASIN'); if (asin) { return baseUrl + asin.value; }; } catch (err) { console.error(`エラーが発生しました (${err})`); } })();
全体
何がベストかを語れるレベルはないので、コア部分をそのまま載せておきます。
backgroud.js
- AmazonのURLかどうかを判定する(ガバガバ)
function isAmazonUrl(pageUrl) { const pattern = 'https://www.amazon.co.jp'; return pageUrl.includes(pattern); }
- 右クリックメニューを追加する
chrome.runtime.onInstalled.addListener(function() { chrome.contextMenus.create({ id: 'open-good-amazon-url', type: 'normal', title: 'Amazonの綺麗なURLを開く', contexts: ['page'] }); });
- 右クリック時の挙動を定義する
chrome.contextMenus.onClicked.addListener(async (info, tab) => { try { const pageUrl = info.pageUrl; if (!isAmazonUrl(pageUrl)) { console.log('Amazon のリンクではありません') return }; const res = await chrome.scripting.executeScript({ target: {tabId: tab.id}, files: ['./content.js'], }); const url = res[0].result; if (url) { chrome.tabs.update({url: url}); }; } catch (err) { console.error(`エラーが発生しました (${err})`); } });
content.js
(再掲)
// DOM にアクセスする (() => { try { const baseUrl = 'https://www.amazon.co.jp/dp/'; const asin = document.getElementById('ASIN'); if (asin) { return baseUrl + asin.value; }; } catch (err) { console.error(`エラーが発生しました (${err})`); } })();
まとめ
途中であげた問題点を再掲します。
- ASIN をURLから確実に取得する方法がわからない
- A-1. 該当ページのDOMが取得できれば、そこから ASIN を取得できるので確実
- A-2. 正規表現で頑張る
- Chrome 拡張の service_worker では DOM を取得できない → そのままでは
A-1.
が使えない
今回できたのは、 A-1.
と B-1.
でした。
本当は初期案のように、該当ページを開く前に綺麗なURLを取得して右クリックで開けるようにしたかったです。
なので、次に無難そうな B-2.
をやるかもしくは(出来るかどうかそもそも知らないのですが) B-3.
も挑戦したいです。
というより B-3.
でなんとかできないか、という方向で頑張ってたんですが、結局何がうまくいってないかのかわからなくなって諦めた感じです。リベンジしたい。
得たもの
- JavaScript ちょっとだけわかった
- JavaScript と Promise, async, await について学ぶことができた
- Chrome 拡張を作ることができた