ブックマークレット版はすでに紹介しましたが、もともとは Chrome 拡張として作りたいと思っていました。
kesumita.hatenablog.com
とりあえず動くものはできたので紹介します。
先に成果物
github.com
初期案
動かし方のイメージとしては、疑惑のリンクを右クリックしたときにコンテキストメニュー(右クリックで出てくるメニュー)の中にその「綺麗な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;
}
});
developer.mozilla.org
ちなみに Promise とか async / await のあたりを全く知らなかったので一から勉強しました。
jsprimer.net
このページがおすすめです。
話を戻しまして、これで取得したURLを加工して余計な文字列を排除すればOKということで作ってみたのですが、URLの加工のパターンを考慮漏れがない形で実装するのが面倒だと思いました。
例えばこちらですが、
www.amazon.co.jp
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
これを開いたページ内で実行するのが、前回のブックマークレット編でした。
gist.github.com
が、自分はブックマークバーを表示しない派(画面を広くしたい)であり、 Chrome 拡張としてのアウトプットもほしいので別の方法を考えます。
manifest v3 では service_worker は DOM にアクセスできない
一番苦労したのはここです。
Chrome 拡張では、 manifest.json というファイルに「どのスクリプトを実行するのか」や「どんなアクションをこの拡張に許可するのか」を定義します。
で、この manifest.json ですが、今は V3 が最新になっています。
developer.chrome.com
この V3 ですが、 V2 と比べて色々変更されたらしく、ググって出てきた V2 時代のやり方を適用しようにも根本的に適用できないパターンがありました。
それがこの service_worker では DOM にアクセスできないという話です。
なので、コンテキストメニューでクリックされたときにページのDOMを読み込んで、先ほどの ASIN を取得して…というのができないです。
問題点を整理
今の問題点を一旦整理します。
- ASIN をURLから確実に取得する方法がわからない
- A-1. 該当ページのDOMが取得できれば、そこから ASIN を取得できるので確実
- A-2. 正規表現で頑張る
- Chrome 拡張の service_worker では DOM を取得できない → そのままでは
A-1.
が使えない
- B-1. 外部スクリプトを呼び出して
A-1.
を実行する
- B-2. fetch API で取得した URL から
A-2.
を実行する(できる?できそう)
- B-3. fetch API で取得した URL からその先の DOM を取得し、
A-1.
を実行する(できる?)
そして A-1.
単体が前回のブックマークレットというわけですね。
本題
いくつか方法はありそうでしたが、 B-1.
の外部のスクリプトで実行するようにしました。
こんな感じです。
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});
};
(() => {
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
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
(再掲)
(() => {
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.
が使えない
- B-1. 外部スクリプトを呼び出して
A-1.
を実行する
- B-2. fetch API で取得した URL から
A-2.
を実行する(できる?できそう)
- B-3. fetch API で取得した URL からその先の DOM を取得し、
A-1.
を実行する(できる?)
今回できたのは、 A-1.
と B-1.
でした。
本当は初期案のように、該当ページを開く前に綺麗なURLを取得して右クリックで開けるようにしたかったです。
なので、次に無難そうな B-2.
をやるかもしくは(出来るかどうかそもそも知らないのですが) B-3.
も挑戦したいです。
というより B-3.
でなんとかできないか、という方向で頑張ってたんですが、結局何がうまくいってないかのかわからなくなって諦めた感じです。リベンジしたい。
得たもの
参考ドキュメント
developer.chrome.com
developer.chrome.com
developer.chrome.com