OAuthではてなブックマークするchrome extentionを作る
OAuthを利用してはてなブックマークするための
chrome extentionを作成しました。
うちの会社からはてなブックマークが禁止されています。
閲覧はできるので、どうやらはてなブックマークへのPOST送信が
禁止されているようです。
httpsで送信すればいいかなと思いましたが、うまくいきません。
はてなの公式のchrome extentionを使用してみましたが、ブックマークできません・・・
これでは何かと不便なのでブックマークできないか考えていました。
はてなブックマークAPI
はてなブックマークドキュメント一覧を見てみると、
REST APIとAtomAPIが使えるようです。
はてなブックマークドキュメント一覧 - Hatena Developer Center
認証はそれぞれ、
・REST API⇒OAuth認証
・Atom API⇒WSSE認証またはOAuth認証
となっています。
WSSE認証が簡単そうなので、WSSEで認証してAtomAPIを使用してみたのですが、
こちらもPOST認証が禁止されているようで、うまくいきません・・・
なので、REST APIを利用することにしました。
OAuth認証
REST APIを利用するにはOAuth認証が必要です。
Consumer key を取得して OAuth 開発をはじめよう - Hatena Developer Center
OAuthではRequest TokenやAccess Tokenを取得する必要があるのですが、
何かライブラリがないかと探していたらありました。
Tutorial: OAuth - Google Chrome
これを使用することにしました。
Chrome OAuth Extension
使い方はAPIのURLとconsumer keyとconsumer secretを設定すれば、
Request TokenやAccess Tokenの取得をよろしくやってくれるので
簡単だなぁ~と思っていたらうまくいきませんでした・・・・
var oauth = ChromeExOAuth.initBackgroundPage({ 'request_url': <OAuth request URL>, 'authorize_url': <OAuth authorize URL>, 'access_url': <OAuth access token URL>, 'consumer_key': <OAuth consumer key>, 'consumer_secret': <OAuth consumer secret>, 'scope': <scope of data access, not used by all OAuth providers>, 'app_name': <application name, not used by all OAuth providers> });
どうやら、Request Tokenを取得する際にoauth_callbackを指定するのですが、
このOAuth Extensionではchromeで生成したタブページのURLになっており、
chrome://xxxxxxxxxxxxxxxxという値になっています。
hatena REST APIではこのURLを受け付けてないようで、500エラーになってしまいます。
OAuth1.0の仕様を確認してみると、oauth_callbackは
httpである必要があるようなのでhatenaの動きは正しいようです。
どうしようかなぁ~と思い、hatena OAuthのマニュアルを見ていると、
下記のような記述が。
URL の代わりに "oob" が指定されていた場合は oauth_verifier の値をユーザーに表示します
ということでoauth_callbackには"oob"を指定します。
これで実行すると、下記のような画面が表示されます。
ここで表示されたoauth_verifierをつけてAPIをコールし
アクセストークンを取得します。
verifierの自動取得
毎回verifierを手で付けるのは面倒なので自動で取得するようにします。
pin.js
window.onload = function() { var pin = document.querySelector(".verifier"); if ((pin !== undefined && pin !== null)) { chrome.extension.sendRequest({ "action" : "getAccessToken", "verifier": pin.innerText.replace(/\r?\n/g,"") }); chrome.extension.sendRequest({ "action" : "closeTab" }); } }
backgrount.jsのイベントリスナーはこんな感じ。
background.js
chrome.extension.onRequest.addListener(function(req, sender) { switch(req.action) { case 'getAccessToken': var reqToken = oauth.getReqToken(); var bookmarkInfo = JSON.parse(oauth.getBookmarkInfo()); oauth.getAccessToken(reqToken, encodeURIComponent(req.verifier), sendPost); break; case 'bookmark': oauth.setBookmarkInfo(JSON.stringify(req)); sendPost(); break; case 'closeTab': chrome.tabs.remove(sender.tab.id, function() {}); } });
これで自動でverifierを取得し、タブを閉じることができました。
popupの作成
Browser actionsのiconを押したときに表示するpopupを作ります。
ブックマークコメントと非公開のチェックボックスを付けました。
素のHTMLだと素っ気ないので軽量なcssを当てました。
軽量cssはたくさんありますが、よさそうなpapier.cssにしました。
papier.min.cssはわずか11kbです。
popup.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="./css/papier.min.css" /> <link rel="stylesheet" type="text/css" href="popup.css" /> </head> <body> <div id="content"> <div id="bookmark"> <h2>はてなブックマーク</h2> <textarea id="comment" name="comment" placeholder="comment..." rows="3" cols="30"></textarea> <p> <input type="checkbox" id="secret" name="secret"><img src="./img/lock_large_locked.png" title="ブックマークを他のユーザに公開しない">非公開 </p> <button class="m bg-light-blue" type="button">ブックマーク</button> </div> </div> <script type="text/javascript" src="popup.js"></script> </body> </html>
popup.js
var tabUrl; window.onload = function() { chrome.tabs.getSelected(null, function (tab) { tabUrl = tab.url; console.debug("tabUrl: " + tabUrl); }); }; function bookmark() { var secret = 0; var comment = document.getElementById("comment").value; if (document.getElementById("secret").checked) { secret = 1; } chrome.extension.sendRequest({ "action" : "bookmark", "bookmarkUrl" : tabUrl, "comment" : comment, "secret" : secret }); window.close(); }; document.addEventListener('DOMContentLoaded', function () { document.querySelector('button').addEventListener('click', bookmark); });
ポップアップはこんな感じ。
APIコール
popupのブックマークボタンを押した後に
background.jsではてなブックマークAPIをコールします。
background.js
function sendPost () { var bookmarkInfo = JSON.parse(oauth.getBookmarkInfo()); oauth.authorize(function() { setIcon(); var restUrl = "http://api.b.hatena.ne.jp/1/my/bookmark"; var request = { 'method': 'POST', 'parameters': { // はてなAPI側でURLを正規化してくれるため、そのまま送信 'url': bookmarkInfo.bookmarkUrl, 'comment' : bookmarkInfo.comment, 'private' : bookmarkInfo.secret } }; oauth.sendSignedRequest(restUrl, showResult, request); }); };
dialogの作成
完了後のダイアログを作成します。
background.jsではてなブックマークAPIのレスポンスを取得し、dialogを表示します。
dialogにもpapier.cssを適用しました。
background.js
var bookmarkRes = {}; function showResult(text, xhr) { bookmarkRes.status = xhr.status; bookmarkRes.statusText = xhr.statusText; chrome.tabs.create({ url: chrome.extension.getURL('dialog.html'), active: false }, function(tab) { chrome.windows.create({ tabId: tab.id, type: 'popup', top: 20, height: 150, width: 400, focused: true }); }); };
dialog.html
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="./css/papier.min.css" /> <title>Dialog</title> </head> <body> <section class="panel"> <header> <div id="output"></div> </header> <main> <div class="center"> <button class="m" type="button">OK</button> </div> </main> </section> <script src="dialog.js"></script> </body> </html>
dialog.js
window.onload = function() { var output = document.querySelector('#output'); var header = document.querySelector('header'); var button = document.querySelector('button'); var div = document.createElement('div'); var message = document.createElement('h4'); var errorMessage = document.createElement('p'); var background = chrome.extension.getBackgroundPage(); if (background.bookmarkRes.status == 200) { header.classList.add("bg-blue"); button.classList.add("bg-light-blue"); message.innerText = "ブックマークしました!"; } else { header.classList.add("bg-red"); button.classList.add("bg-blue-grey"); message.innerText = "ブックマークに失敗しました・・・"; errorMessage.innerText = background.bookmarkRes.status + " : " + background.bookmarkRes.statusText; }; div.appendChild(message); div.appendChild(errorMessage); output.appendChild(div); }; document.addEventListener('DOMContentLoaded', function () { document.querySelector('button').addEventListener('click', function () { window.close(); }); });
成功時
失敗時
manifest.json
manifest.jsonでicon、background、コンテントスクリプト、ブラウザアクション、
パーミッションの設定をして完了。
manifest.json
{ "manifest_version": 2, "name": "hatena bookmarker", "version": "0.1.0", "author": ["pppurple"], "description": "Enable to bookmark at hatena", "icons": { "48": "img/fav.png", "128": "img/fav.png" }, "background": { "scripts": [ "chrome_ex_oauthsimple.js", "chrome_ex_oauth.js", "background.js" ] }, "content_scripts": [ { "js": [ "pin.js" ], "matches": [ "https://www.hatena.ne.jp/oauth/authorize" ], "run_at": "document_end" } ], "browser_action": { "default_title": "hatena bookmarker", "default_icon": "img/hatebu_icon_off.png", "default_popup": "popup.html" }, "permissions": [ "tabs", "https://www.hatena.com/oauth/initiate", "https://www.hatena.ne.jp/oauth/authorize", "https://www.hatena.com/oauth/token", "http://api.b.hatena.ne.jp/1/my/bookmark/*" ], "web_accessible_resources": [ ] }
これではてなブックマークすることができました。
ソースはこちらにあげときました。
github.com