背景
- amazonの商品ページをチャットでやりとりする機会があり、長いURLがウザかった。メールに貼りつけると折り返されてURLが途切れてしまうこともあるかと(いまどき無い!?)
- wikiを引用するときもURLが長い
例
amazon, wiki のURLは短くできる。(≠短縮URL)
amazonは商品コードで表示できる
https://www.amazon.co.jp/dp/B07P6VVPLV
https://www.amazon.co.jp/dp/B077YCQ265
wikiは記事のidで表示できる
https://ja.wikipedia.org/?curid=4698
短縮URLはサービス経由で短くするので、サービス終了になると使えなくなってしまう。
以下はアイルトンセナのwikiのURLを短くしたもの
https://bit.ly/30tuVIC
amazon, wiki のURLを短くするツールはあったけどめんどくさい
chrome extension で短いURLをサクっとコピーできるようにしよう
3年ぶりくらいにchrome extension に着手。下記ブレストも参照
仕様
- 右クリメニューでクリップボードにコピー
- アイコンでクリップボードにコピー
- 対象外のサイトでは右クリメニューを出さない
- 対象外のサイトではアイコンを無効化 ← 実現できず@TODO
ブレスト
-
右クリメニューはサクっと使えるので決まり。以前、作ったこともある。
-
貼り付けが目的なので、クリップボードにコピーしたい。→調べたら可能だったので採用
-
アイコンは使用しないつもりが、勝手に表示されるので、押したらコピーできるようにした。
-
右クリメニューを押したときコピーされたか分からないので、メッセージを出そうと思ったが、それもウザいので、対象のサイトのみ右クリメニューが出るようにした。
-
コピーできてないことがある(wikiのみ)ので、アイコンにコピー状況を表示するようにした。(原因は、contensとbackgroundの通信失敗)
-
対象外のサイトでアイコン無効化ができなかった。エクステンションは他にも使用しており、アイコンは表示しないつもりなのでOK(^^;
-
アイコンでコピーもわりと便利。
-
スマホでも使いたい→無理でした
-
IEでも使いたい→しらん
ファイル構成
root_folder
│ manifest.json
│
├─html
│ options.html
│
├─img
│ icon-128x128.png
│ icon-16x16.png
│ icon-48x48.png
│
├─js
│ background.min.js
│ content.min.js
│
└─_locales
├─en
│ messages.json
└─ja
messages.json
ソース
- manifest.json
permissions はトップドメインを*にできないため、https://www.amazon.*/* はエラーとなる。
{
"name": "short url for amazon, wiki",
"short_name": "short url",
"version": "1.0.0",
"manifest_version": 2,
"description": "short url to clipboard",
"author": "ctrlzr",
"default_locale": "ja",
"permissions": [
"https://www.amazon.co.jp/*",
"https://www.amazon.com/*",
"https://www.amazon.it/*",
"https://www.amazon.fr/*",
"https://www.amazon.de/*",
"https://www.amazon.es/*",
"https://*.wikipedia.org/wiki/*",
"activeTab",
"background",
"contextMenus",
"clipboardWrite"
],
"content_scripts": [{
"matches": [
"https://www.amazon.co.jp/*",
"https://www.amazon.com/*",
"https://www.amazon.it/*",
"https://www.amazon.fr/*",
"https://www.amazon.de/*",
"https://www.amazon.es/*",
"https://*.wikipedia.org/wiki/*"
],
"js": ["js/content.min.js"]
}],
"background": {
"scripts": ["js/background.min.js"]
},
"browser_action": {
"default_icon": "img/icon-16x16.png",
"default_title": "short url"
},
"options_ui": {
"page": "html/options.html",
"chrome_style": true
},
"icons": {
"128": "img/icon-128x128.png",
"48": "img/icon-48x48.png",
"16": "img/icon-16x16.png"
}
}
- content.js
chrome.runtime.onMessage.addListener で background のメッセージを受け取り、wikiの記事idをhtml内から取得する。
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
var res = null;
if (!msg.target) {
//
} else {
if ('wiki' === msg.target) {
res = get_wgArticleId();
} else {
console.log(msg.target+' does not supprted');
}
}
sendResponse(res);
});
function get_wgArticleId() {
let scripts = document.querySelectorAll('script');
for (let i=0; i<scripts.length; i++) {
let script = scripts[i];
let result = script.innerHTML.match(/"wgArticleId":(\d+)/);
if (result) {
return result[1];
}
}
return null;
}
- background.js
cm_click でasyncしてるのは、wikiでhtmlを捜索するのに、contents_script と通信するため。
どのイベントが走るか console.log で確認して必要なイベントを見極めた。
chrome.tabs.onActivated:タブを切り替えたとき
chrome.tabs.onUpdated:画面遷移、再読み込みしたとき
chrome.browserAction.onClicked:アイコンをクリックしたとき
以下は非同期処理
chrome.tabs.sendMessage
chrome.tabs.getSelected
var cmid = null;
var cm_click = async function(data, tab) {
var color = [0, 0xaa, 0, 100];
let txt = await get_short_url[data.menuItemId].call(this, data.pageUrl, tab.id);
if (!txt) {
txt = data.pageUrl;
color = [0xff, 0, 0, 100];
}
let textArea = document.createElement('textarea');
textArea.value = txt;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
chrome.browserAction.setBadgeText({text:"copy", tabId: tab.id});
chrome.browserAction.setBadgeBackgroundColor({color: color});
};
var icon_click = async function(tab) {
var data = {
menuItemId: "wiki",
pageUrl: tab.url
};
cm_click(data, tab);
};
var get_short_url = {
amazon: function(url) {
let pos = url.indexOf('/dp/');
if (-1 === pos) {
return null;
}
let pos2 = url.indexOf('/', pos+4);
if (-1 === pos2) {
pos2 = url.indexOf('?', pos+4);
}
let id = null;
if (-1 < pos2) {
id = url.substring(pos+4, pos2);
} else {
id = url.substring(pos+4);
}
let path = getBaseUrl(url)+'/dp/'+id;
return path;
},
wiki: function(url, tab_id) {
return new Promise(function(resolve, reject) {
chrome.tabs.sendMessage(tab_id, {target: "wiki"}, function(id) {
if (id) {
let path = getBaseUrl(url)+'/?curid='+id;
resolve(path);
} else {
resolve(null);
}
});
});
}
};
function getBaseUrl(url) {
let pos = url.indexOf('/', 8);
return url.substring(0,pos);
}
var cm = function() {
chrome.tabs.getSelected(null,function(tab){
let target = null;
if (!tab.url) {
} else if (tab.url.match(/^https:\/\/www\.amazon\.[^/]+\/([^\/]*\/|)dp\//)) {
target = 'amazon';
} else if (tab.url.match(/^https:\/\/.*\.wikipedia.org\//)) {
target = 'wiki';
} else {
//
}
if (cmid) {
chrome.contextMenus.remove(cmid);
cmid = null;
}
if (target) {
let options = {
id: target,
title: "short url to clipboard",
contexts: ['page'],
onclick: cm_click
};
cmid = chrome.contextMenus.create(options);
}
});
}
chrome.tabs.onActivated.addListener(function (tabId) {
cm();
});
chrome.tabs.onUpdated.addListener(function (tabId) {
cm();
});
chrome.browserAction.onClicked.addListener(function(tab){
if (!cmid) {
return;
}
cm_click({menuItemId: cmid, pageUrl: tab.url}, tab);
});
- その他のソースはほぼ使用していないので省略
注意点
公開は有料(5$)
限定公開は無料という情報もあったが、現在(2019年7月)は、有料。
20本まで5$
manifest.json の permissions に <all_urls> , tab を使用しない
chrome extension の公開には審査があり、<all_urls> にすると審査が長くなる。tab は activeTab を推奨される。権限ありありでどんなことができるかは、こちらを参照
ブラウザ拡張の権限でどこまで(悪いことを)できるのか?とその対策【デモあり】
permissions は適切に!
manifst.json内のpermissionsは必要最小限にすること。
紆余曲折はこちら
拡張外の何かを読み込む処理は、プライバシーポリシーに抵触するおそれあり。
localStorageは問題なし(たぶん)
minifyは不要
PCでは通信量なんて気にするな、ということらしい。紆余曲折はこちら
手間暇かけて、というほどでもないけど、minifiyで拒否られるくらいなら、何もしないほうがいい。
document は content_scripts のみ
manifest.json の content_scripts に指定したjsのみ、documentを読める。
backend で document.getElementById('honya') としても想定した値は返らない。
tabs は background のみ
content_scripts 側で、chrome.tabs がエラーになってハマった。
非同期を前提に
普通にjsで書いていたら非同期通信を余儀なくされ、構成が変わってしまい、aync await で回避する羽目に。
自動更新は使えない
2017年12月以降、自動更新する機能は制限された。
以前は、manifest.json に update_url を記載すれば自動更新できた。らしい。
公開
思ったより審査に手間取ったので別にしました
ようやく公開されました。まだ検索しても出ないのでurlを載せておきます。