デモ
特徴
- archive.is, archive.org, gyo.tc, googleのキャッシュ、と計四か所から魚拓のURLを探します!
- vue.js製(やってみたかった)
なぜ作ったか
ハテブで魚拓コメがトップだったりして、需要を感じた。
後普通に自分が使いたかった。
そして、Qiitaユーザーの皆さんも必要性を感じてくださっている事と思います^^。
どう作ったか
Chorome拡張はHTML+CSS+javascriptの基本セットで作れます。
ファイル構成は以下のようになりました。
- manifest.json(どの拡張でも必須)
- popup.js
- popup.html
- popup.css
- jquery
- vue.js
- icons(フォルダ。配下にicon16.png, icon48.png, icon128.png)
詳しく見ていきましょう。
manifest.jsonは、Chrome拡張のまとめ役みたいな感じ。
{
"name": "FindGyotaku in Console",
"version": "0.0.1",
"manifest_version": 2,//2で固定する
"permissions": ["tabs", "http://*/", "http://*/"],//tabsはアクティブなタブのURLを取得するため、http://*/,https://*/はChrome拡張からすべてのサイトへのアクセスを許可するために必要
"description": "Show all Gyotaku",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon16.png"
},
"browser_action": {
"default_popup": "popup.html"//ここでpopupのhtmlを指定
}
}
そして、popup.jsが今回の大事ポイントです。
let vue;
//https://gyo.tc/から
function findGyotakuByUrl(url) {
return new Promise(resolve => {
console.log("findGyotakuByUrl Started");
$.get("https://gyo.tc/" + url, null,
function (data) {
console.log(data);
let gyotakuList_ = new DOMParser()
.parseFromString(data, "text/html")
.querySelectorAll("[id^=fish]");
resolve(gyotakuList_)
},
"html"
);
})
}
//https://archive.is/から
function findInternetArchiveByUrl(url) {
return new Promise(resolve => {
$.get(`https://archive.is/${url}`, function (data) {
let rowsList =
new DOMParser()
.parseFromString(data, "text/html")
.querySelectorAll("[id^=row]");
let archivesList = [];
rowsList.forEach(row => {
archivesList.push(
Array.from(
row.querySelectorAll("[style^=text\\-decoration\\:none]")
)
)
});
resolve(archivesList.flat());
console.log(`flatten archivesList is ${archivesList.flat()}`);
})
})
}
//googleのwebcacheから
function findGoogleCache(url) {
return new Promise(resolve => {
$.get(`http://webcache.googleusercontent.com/search?q=cache:${url}`, function () {
resolve(`http://webcache.googleusercontent.com/search?q=cache:${url}`);
})
})
}
//http://web.archive.orgから
function findWaybackMachineLatest(url) {
return new Promise(resolve => {
//limit=-1で最新
$.get(`http://web.archive.org/cdx/search/cdx?limit=-1&fl=timestamp&url=${url}`, function (timestamp) {
resolve(`https://web.archive.org/web/${timestamp}/${url}`);
console.log(`waybackMachine response(latest) is ${timestamp}`);
})
})
}
//http://web.archive.orgから
function findWaybackMachineOldest(url) {
return new Promise(resolve => {
//limit=1で最古
$.get(`http://web.archive.org/cdx/search/cdx?limit=1&fl=timestamp&url=${url}`, function (timestamp) {
resolve(`https://web.archive.org/web/${timestamp}/${url}`);
console.log(`waybackMachine response(latest) is ${timestamp}`);
})
})
}
console.log("popup.jsLoaded");
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabsArray) {
console.log(`currentUrl is ${tabsArray[0].url}`);
findGyotakuByUrl(tabsArray[0].url).then((gyotakuList) => {
console.log(gyotakuList);
gyotakuList.forEach(element => {
vue.items.push(element)
});
vue.isLoadingGyotaku = false
}).catch((err) => {
console.log(err);
vue.isLoadingGyotaku = false
});
findInternetArchiveByUrl(tabsArray[0].url).then((archivesList) => {
console.log(archivesList);
archivesList.forEach(element => {
vue.items.push(element)
});
vue.isLoadingArchives = false
}).catch((err) => {
console.log(err);
vue.isLoadingArchives = false
});
findGoogleCache(tabsArray[0].url).then((googleCacheUrl) => {
console.log({
href: googleCacheUrl
});
vue.items.push({
href: googleCacheUrl
});
vue.isLoadingGoogleCache = false
}).catch((err) => {
console.log(err)
});
findWaybackMachineLatest(tabsArray[0].url).then((waybackMachineUrl) => {
console.log({
href: waybackMachineUrl
});
vue.items.push({
href: waybackMachineUrl
});
vue.isLoadingWaybackMachine = false
}).catch((err) => {
console.log(err)
});
findWaybackMachineOldest(tabsArray[0].url).then((waybackMachineUrl) => {
console.log({
href: waybackMachineUrl
});
vue.items.push({
href: waybackMachineUrl
});
vue.isLoadingWaybackMachine = false
}).catch((err) => {
console.log(err)
});
});
$(function () {
vue = new Vue({
el: '#example-1',
data: {
items: [],
isLoadingGyotaku: true,
isLoadingArchives: true,
isLoadingGoogleCache: true,
isLoadingWaybackMachine: true,
}
})
});
分割して解説します。
function findGyotakuByUrl(url) {
return new Promise(resolve => {
$.get("https://gyo.tc/" + url, null,
function (
let gyotakuList_ = new DOMParser()
.parseFromString(data, "text/html")
.querySelectorAll("[id^=fish]");
resolve(gyotakuList_)
},
"html"
);
})
}
↑要するにスクレイピングですね。javascriptではquerySelectorが便利です。
https://gyo.tc/ 以下にURLを投げると魚拓一覧が返るので、開発者ツールから頑張って解析しました。
function findInternetArchiveByUrl(url) {
return new Promise(resolve => {
$.get(`https://archive.is/${url}`, function (data) {
let rowsList =
new DOMParser()
.parseFromString(data, "text/html")
.querySelectorAll("[id^=row]");
let archivesList = [];
rowsList.forEach(row => {
archivesList.push(
Array.from(
row.querySelectorAll("[style^=text\\-decoration\\:none]")
)
)
});
resolve(archivesList.flat());
})
})
}
↑こちらもhttps://archive.is/ 以下にURLを投げると魚拓一覧が返るので、こちらも開発者ツールから頑張って解析しました。
Array.flat()はMDNで分かるように実験的なヤツで、配列を好きな次元に均せる関数です。デフォルトで一次元なのでそのまま使用しました。
function findGoogleCache(url) {
return new Promise(resolve => {
$.get(`http://webcache.googleusercontent.com/search?q=cache:${url}`, function () {
resolve(`http://webcache.googleusercontent.com/search?q=cache:${url}`);
})
})
}
↑言うことなし。思ったより簡単。
function findWaybackMachineLatest(url) {
return new Promise(resolve => {
//limit=-1で最新
$.get(`http://web.archive.org/cdx/search/cdx?limit=-1&fl=timestamp&url=${url}`, function (timestamp) {
resolve(`https://web.archive.org/web/${timestamp}/${url}`);
})
})
}
function findWaybackMachineOldest(url) {
return new Promise(resolve => {
//limit=1で最古
$.get(`http://web.archive.org/cdx/search/cdx?limit=1&fl=timestamp&url=${url}`, function (timestamp) {
resolve(`https://web.archive.org/web/${timestamp}/${url}`);
console.log(`waybackMachine response(latest) is ${timestamp}`);
})
})
}
↑apiが有って助かりました。
WaybackMachineのアーカイブは膨大なので最古&最新のみ取得しています。
limit=1で最古、limit=-1で最新を指定、fl=timestampでレスポンスをtimestampのみに限定します。
WaybackMachineはtimestampとURLを指定すると(珍しく)個別ページのURLが分かるので、timestampだけで十分なのです。
$(function () {
vue = new Vue({
el: '#example-1',
data: {
items: [],//以下デフォルト値を指定しています
isLoadingGyotaku: true,
isLoadingArchives: true,
isLoadingGoogleCache: true,
isLoadingWaybackMachine: true,
}
})
});
<p v-if="isLoadingGyotaku">
魚拓から読み込んでいます
</p>
↑出ましたvue.js!読み込み中のメッセージを出しています。
vue.jsを使うと、HTMLとjs間でバインディングができます。
例えば、
HTMLのv-if属性にisLoadingGyotakuを指定
↓
js側でvueインスタンス作成(vueのdata属性に、isLoadinGyotakuをメンバにしたオブジェクトを付けたもの)
↓
vue.isLoadingGyotakuにアクセスできるように&
vue.isLoadingGyotakuがv-if="isLoadingGyotaku"とバインドされる
って感じ。
つまり、isLoadingGyotaku=trueならpタグが見えて、falseなら見えなくなる!
これは素晴らしいですね。本当に素晴らしい。
ほかにもいろいろな属性が有ります。だいたいv-が付く。
<ul>
<li v-for="item in items">
<a href="{{ item.href }}" target='_newtab'>
{{ item.href }}
</a>
</li>
</ul>
↑v-forとか。見つかったURLを表示するために使っています。
{{}}はテンプレートリテラルみたいに使えます。
↓ラスト
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabsArray) {
console.log(`currentUrl is ${tabsArray[0].url}`);
findGyotakuByUrl(tabsArray[0].url).then((gyotakuList) => {
console.log(gyotakuList);
gyotakuList.forEach(element => {
vue.items.push(element)
});
vue.isLoadingGyotaku = false
}).catch((err) => {
console.log(err);
vue.isLoadingGyotaku = false
});
findInternetArchiveByUrl(tabsArray[0].url).then((archivesList) => {
console.log(archivesList);
archivesList.forEach(element => {
vue.items.push(element)
});
vue.isLoadingArchives = false
}).catch((err) => {
console.log(err);
vue.isLoadingArchives = false
});
findGoogleCache(tabsArray[0].url).then((googleCacheUrl) => {
console.log({
href: googleCacheUrl
});
vue.items.push({
href: googleCacheUrl
});
vue.isLoadingGoogleCache = false
}).catch((err) => {
console.log(err);
vue.isLoadingGoogleCache = false
});
findWaybackMachineLatest(tabsArray[0].url).then((waybackMachineUrl) => {
console.log({
href: waybackMachineUrl
});
vue.items.push({
href: waybackMachineUrl
});
vue.isLoadingWaybackMachine = false
}).catch((err) => {
console.log(err)
vue.isLoadingWaybackMachine = false
});
findWaybackMachineOldest(tabsArray[0].url).then((waybackMachineUrl) => {
console.log({
href: waybackMachineUrl
});
vue.items.push({
href: waybackMachineUrl
});
vue.isLoadingWaybackMachine = false
}).catch((err) => {
console.log(err)
vue.isLoadingWaybackMachine = false
});
});
↑async/awaitで見やすくなるのは分かってるんだけどね……面倒なのでそのまま。
chrome.tabs.queryで今見てるタブのURLを取得後、非同期×5を回しています。
chrome.系はChrome拡張の時しか使えないapiで、ほかにも色々便利です。
はまったポイント
最初
let fuga = true
vue = new Vue({
el: '#example-1',
data: {
isLoadingGyotaku: fuga,
}
})
fuga = false//バインドされてるはずなのに動かない!!!
って事が有って大変でしたね。本当は
vue = new Vue({
el: '#example-1',
data: {
isLoadingGyotaku: true,
}
})
vue.isLoadingGyotaku = false//バインドされてて動く!!!
で良かったっていう。公式ドキュメントをちゃんと読みましょう。
後、動作確認にevent pagesを使ってしまって、popupのみで動くapiを動かせなかったりとか……
popupページで検証する方法を知らなかったとか……(左クリックでポップアップされたのを右クリ)
計何時間使ったでしょうかね。今はどうでも良いけど。
最後に
2020/04に大学生になる(浪人しなければ!)ので、良かったらインターンに誘ってください。
github:https://github.com/negitorogithub
gmail:bakeratta35@gmail.com
お読み頂きありがとうございました。 ~~旦⊂(・∀・ )