理系学生日記

おまえはいつまで学生気分なのか

ブラウジング中のページがブックマーク済かを表示する Greasemonkey UserScript

つくった。

概要

今ブラウジング中のページを既にはてなブックマークしているかを表示する UserScript です。

はてなブックマークエントリー情報取得APIとは - はてなキーワード - Vimperator

需要

ぼくは、見ているページをブクマするとき、Tombloo という Firefox Addon を使って、はてブと同時に Evernote にも転送しています。
これはなぜかというと、Evernote の全文検索機能を使いたいからですね。ブックマークのタグは忘れるし、表記が揺れるし、大量になればなるほど管理が面倒になります。その点全文検索は非常に楽だ。はてブにも全文検索機能があるけど、あれはみなさんのような金と社会的ステータスを両立しているリッチメンのための機能であって、毎夜奨学金返済にうなされているぼくには高嶺の花です。

ところが Tombloo には、既にブクマ済かどうかを調べる機能はついていません。つまり、同じページを何度も何度も Evernote に転送してしまうというような可能性を捨てきれない。重複はムダです。ムダなのは許してはいけないし、即刻排除すべきだ。

そこで、こういう UserScript を一生懸命つくったわけですね。とてもよかったとおもいます。

実装

実装としては、はてなブックマークエントリー情報取得API を呼び出して、自分のはてな ID のブックマークエントリがあれば Bookmark 済という表示を出しているだけです。
レンダリングの度に当該 API を呼び出すのはムダだし、ムダは排除すべきなので、「b」キーを押したときに API を呼びだすようにしてます。

API 仕様に明記されてないんだけど、一件もブックマークされていない URL に対する API 応答は、GM_xmlhttpRequest の responseText が null になって返ってくるっぽい。気付くまで随分かかった。

追記:

// ==UserScript==
// @name           AlreadyHatenaBookmarked
// @namespace      com.kiririmode
// @description    Have you already hatena-bookmarked this page?
// @include        http://*
// @include        https://*
// ==/UserScript==

(function() {

    const HATENA_ID = 'your hatena id';
    const INITIAL_OPACITY = 0.9;

    const CSS_DIV_ID  = 'AHBdiv';
    const CSS_TEXT_ID = 'AHBtext';
    const KEY_FIRE    = 'b';

    init();

    function init() {
        addGlobalStyle([
            'div#' + CSS_DIV_ID  + ' { bottom: 20px; right: 0px; position: fixed; width: 300px; height: 20px; background: #00CED1; z-index: 1000 }',
            'div#' + CSS_TEXT_ID + ' { color: black }'
        ].join("\n"));

        window.addEventListener("keypress", function (evt) {
            switch( String.fromCharCode(evt.charCode) ) {
              case KEY_FIRE: fire();    break;
            }
        }, true );
    }

    function fire() {
        var div = document.getElementById(CSS_DIV_ID);
        if ( div ) {
            // havs already fired at least once
            div.style.opacity = INITIAL_OPACITY;
            fadeout(div);
        }
        else {
            var url = 'http://b.hatena.ne.jp/entry/jsonlite/?url=';

            url += encodeURIComponent(location.href);
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onerror: function(res) {
                    GM_log("error: " + res.statusText);
                },
                onload: function(res) {
                    display(res.responseText);
                    fadeout(document.getElementById(CSS_DIV_ID));
                }
            });
        }
    }

    function fadeout(elem) {
        var opacity = elem.style.opacity || INITIAL_OPACITY;
        opacity -= 0.04;
        opacity = (opacity > 0)? opacity : 0;

        elem.style.opacity = opacity;
        if ( opacity == 0 )
            return;

        window.setTimeout( function() { fadeout(elem); }, 100 );
    }

    function display(responseText){
        if ( responseText == 'null' ) {
            createDisplayBox("not bookmarked");
            return;
        }
        var obj = JSON.parse(responseText);
        var bookmarks = obj.bookmarks;

        for ( var idx = 0; idx < bookmarks.length; idx++ ) {
            if ( bookmarks[idx].user != HATENA_ID ) {
                continue;
            }

            var msg = bookmarks[idx].timestamp + " already bookmarked: \n" +  bookmarks[idx].tags;
            createDisplayBox(msg);
            return;
        }
        createDisplayBox("not bookmarked");
    }

    function addGlobalStyle(css) {
        var head = document.getElementsByTagName('head')[0];
        if ( !head ) { return; }

        var style = document.createElement('style');
        style.type      = 'text/css';
        style.innerHTML = css;

        head.appendChild(style);
    }

    function createDisplayBox(msg) {
        var textnode = document.createTextNode(msg);
        var textBox  = document.createElement("div");
            textBox.setAttribute('id', CSS_TEXT_ID);
            textBox.appendChild(textnode);

        var divBox = document.createElement("div");
            divBox.setAttribute('id', CSS_DIV_ID);
            divBox.appendChild(textBox);

        document.body.appendChild(divBox);
    }
})();