Hatena Referer Viewer バージョンアップ

HatenaRefererViewer.user.js

Yet Another Hatena Referer Viewer

// ==UserScript==
// @name           Hatena Referer Viewer
// @namespace      http://d.hatena.ne.jp/nozom/
// @author         nozom <nozom.kaneko@gmail.com>
// @description    Rearranges Hatena Diary's referer view.
// @include        http://d.hatena.ne.jp/*
// @include        http://*.g.hatena.ne.jp/*
// ==/UserScript==

// ver 2007-01-24

(function() {
    /*===== 設定 =====*/

    // はてなキーワード //
    var patKeywords = {
        '^http://(d2?|diary).hatena.ne.jp/keyword(mobile|diary|stats)?/([^\\?]+).*': '$3',
        '^http://(\\w+).g.hatena.ne.jp/keyword(mobile|diary)?/([^\\?]+).*': '[group:$1] $3',
        '^http://b.hatena.ne.jp/keyword/(.+)': '?B $1',
        '^http://r.hatena.ne.jp/keyword/([^/]+)$': '?R $1',
    };

    // はてなダイアリー・グループ //
    var patDiaries  = {
        '^http://(d|diary).hatena.ne.jp/([\\w-]+)/.*': 'id:$2',
        '^http://(\\w+).g.hatena.ne.jp/([\\w-]+)/.*': 'g:$1:id:$2',
        '^http://(\\w+).g.hatena.ne.jp/.*': 'g:$1',
        '^http://(d|diary).hatena.ne.jp/http\\?(.*)': '?D url(http:$2)',
    };

    // アンテナ //
    var patAntennae = {
        '^http://(a|antenna).hatena.ne.jp/([\\w-]+)/.*': '?A(id:$2)',
        '^http://(a|antenna).hatena.ne.jp/([^/]*)': '?A $2',
        '^http://i-know.jp/([\\w-]+).*': '?i-know($2)',
    };

    // ブックマーク //
    var patBookmarks = {
        '^http://b.hatena.ne.jp/t/([\\w-]+).*': '?B tag:$1',
        '^http://b.hatena.ne.jp/t\\?.*tag=([^&]*).*': '?B tag:$1',
        '^http://b.hatena.ne.jp/entry/(.+)': '?B ($1)',
        '^http://b.hatena.ne.jp/([\\w-]+)/$': '?B(id:$1)',
        '^http://b.hatena.ne.jp/([\\w-]+)(/.+)': '?B(id:$1) $2',
        '^http://b.hatena.ne.jp/(hotentry|entrylist.*)': '?B $1',

        '^http://clip.livedoor.com/clips/([^/]*)(.*)': 'livedoor clip($1) $2',
        '^http://del.icio.us/([^/?]*)(/.+)': 'del.icio.us($1) $2',
        '^http://del.icio.us/([^/?]*)?.*': 'del.icio.us($1)',
    };

    // RSSリーダー //
    var patRSS = {
        '^http://r.hatena.ne.jp/([\\w-]+)/$': '?R(id:$1)',
        '^http://r.hatena.ne.jp/([\\w-]+)/(.+?)/': '?R(id:$1) /$2/',
        '^http://r.hatena.ne.jp/([\\w-]+)/(.+?)/(http.+)': '?R(id:$1) /$2/ ($3)',
        '^http://r.hatena.ne.jp/([\\w-]+)/(http.+)': '?R(id:$1) ($3)',
        '^http://keyword.livedoor.com/w/(.*)': 'LDR(keyword:$1)',
    };

    // はてなアイデア //
    var patIdeas = {
        '^http://i.hatena.ne.jp/idea/(\\d+)': '?I:$1',
    };

    // その他はてな内 //
    var patHatena = {
        '^http://(d|diary)?.hatena.ne.jp/?([\\w-]*)': '?D $2',
        '^http://(\\w+).g.hatena.ne.jp/?([\\w-]*)': '?G($1) $2',
        '^http://g.hatena.ne.jp/?([\\w-]*)': '?G $1',
        '^http://f.hatena.ne.jp/?(\\w*)': '?F $1',
        '^http://www.hatena.ne.jp/(\\d+)/': 'question:$1',
    };

    // 検索エンジン //
    /* tDiary-users Project Wiki内
     * http://tdiary-users.sourceforge.jp/cgi-bin/wiki.cgi?referer_table
     * から一部コピーさせてもらいました。
     * ライセンス: Creative Commons License
     * http://creativecommons.org/licenses/by-nc/1.0/
     */
    var patSearches = {

        // はてなダイアリー
        '^http://search.hatena.ne.jp/(web)?search\\?word=([^&]*)(&.*)?': '?Search($2)',

        // Google
        '^http://www.google.([^/]+)/.*?(\\Wq|as_q)=([^&]*).*': 'Google($3)',
        '^http://www.google.([^/]+)/search.*?(\\Wq|as_q)=cache:[^:]*:([^\\s\\+]+)(\\s|\\+)([^&]*).*': 'Google($5) [Cache:$3]',
        '^http://blogsearch.google.com/blogsearch.*?\\Wq=([^&]*).*': 'Google($1)',
        // '^http://images.google.([^/]+)/.*?imgurl=([^&]*).*': 'Google image($2)',
        // '^http://\\w+.google.([^/]+)/search.*?(\\Wq|as_q)=cache:[^:]*:([^\\s\\+]+)(\\s|\\+)([^&]*).*': 'Google($5) [Cache:$3]',
        // '^http://216.239.\\d+.\\d+/.*?(\\Wq|as_q)=cache:[^:]*:([^\\s\\+]+)(\\s|\\+)([^&]*).*': 'Google($4) [Cache:$2]',
        // '^http://\\w+.google.([^/]+)/.*?(\\Wq|as_q)=([^&]*).*': 'Google($3)',
        // '^http://216.239.\\d+.\\d+/.*?(\\Wq|as_q)=([^&]*).*': 'Google($2)',
        // '^http://google/search.*?\\Wq=([^&]*).*': 'Google($1)',

        // Yahoo
        '^http://([^\.]*\.)?search.yahoo.(com|co\\.jp)/.*?p=([^&]*).*': 'Yahoo($3)',
        '^http://new.search.yahoo.([^/]+)/.*\\Wp=([^&]*).*': 'Yahoo($2)',
        '^http://websearch.yahoo.(com|co\\.jp)/.*?p=([^&]*).*': 'Yahoo($2)',

        // goo
        '^http://(www|search|ocn(search)?|blog|mixi.search).goo.ne.jp/.*?MT=([^&]*).*': 'goo($3)',

        // livedoor
        '^http://.*?livedoor.com/.*?search\\?q=([^&]*).*': 'livedoor($1)',

        // MSN
        '^http://.*search.msn(\\.[^/]+)/.*?[\\?&]x(q|MT)=([^&]*).*': 'msn($3)',
        '^http://.*search.msn.*?[\\?&](q|MT)=([^&]*).*': 'msn($2)',

        '^http://(www|ocn|apple|so-net|dion|odn|hi-ho|sleipnir).excite.co.jp/.*?(search|s)=([^&]*).*': 'excite($3)',

        '^http://.*?matome.jp/(tag|keyword)/(.*?)(.html)?$': 'matome.jp($2)',

        '^http://(search.)?(www.)?infoseek.co.jp/.*?qt=([^&]*).*': 'Infoseek($3)',
        '^http://search.odn.ne.jp/.*?(QueryString|key)=([^&]*).*': 'ODN($2)',
        '^http://.*?lycos.(co\\.jp|com)/?.*(query|qt?)=([^&]*).*': 'Lycos($3)',
        '^http://search.fresheye.com/.*?kw=([^&]*).*': 'FreshEye($1)',
        '^http://(a?search|www).nifty.com/.*?(q|Text)=([^&]*).*': '@nifty($3)',
        '^http://odin.ingrid.org/.*?key=([^&]*).*': 'ODiN($1)',
        '^http://search.naver.co.jp/search.naver.*?query=([^&]*).*': 'NAVER($1)',
        '^http://cgi.search.biglobe.ne.jp/cgi-bin/.*?q=([^&]*).*': 'BIGLOBE($1)',
        '^http://www.ceek.jp/.*q=([^&]*).*': 'ceek.jp($1)',
        '^http://.*?.aol.com/.*query=([^&]*).*': 'AOL Search($1)',
        '^http://www.euroseek.com/.*string=([^&]*).*': 'euroseek.com($1)',

        // '^http://(www.)?technorati.jp/search/search.html.*\\?.*query=([^&]*).*': 'technorati.jp($2)',
        '^http://(www.)?technorati.jp/search/([^\?&]*).*': 'technorati.jp($2)',
        '^http://(www.)?technorati.com/search/([^\\?&]*).*': 'technorati.com($2)',
        '^http://(www.)?technorati.com/tags?/([^\\?&]*).*': 'technorati.com(Tags:$2)',

        '^http://www.yahoogle.jp/(.*).html': 'yahoogle($1)',

        '^http://ask.jp/blog.asp\\?.*?q=([^&]*).*': 'ask.jp($1)',
        '^http://(cybozu|duogate).excite.co.jp/search.gw\\?.*search=([^&]*).*': 'excite($2)',
        '^http://search.blogger.com/.*\\?.*?q=([^&]*).*': 'blogger($1)',
        '^http://bulkfeeds.net/.*?q=([^&]*).*': 'bulkfeeds($1)',

        // '^http://crooz.jp/ex/([^/]+)/k/proxy_i.jsp\\?(.*)query=([^&]*).*': '$1($3)',
        // '^http://crooz.jp/k/search.jsp\\?.*query=([^&]*).*': 'crooz($1)',

        '^http://bst.blogpeople.net/search_result.jsp?.*keyword=([^&]*)': 'blogpeople($1)',
        '^(http://trendlink.mirailab.com/.*)': 'trendlink($1)',
        '^(http://tl.milabo.net/.*)': 'trendlink($1)',
        // '^(http://animemo.seesaa.net/article/.*)': 'animemo($1)',
        '^(http://www.blognavi.com/entry/.*)': 'blognavi($1)',
        '^(http://(www.)?1470.net/bm/urlinfo/.*)': 'blogmap($1)',
        // '^(http://clipsubject.com/ac/.*)': 'clipsubject($1)',

        '^http://ezsch.ezweb.ne.jp/search/.*query=([^&]*).*': 'ezsch($1)',

        '^http://www.namaan.net/result?.*query=([^&]*).*': 'NAMAAN($1)',

        // 取りこぼし
        // '^http://([^\\?/]+).*\\?.*&?(q|p|search|key)=([^&]*).*': '$1($3)',
    };

    // asin検索 //
    var patAsin = {
        '^http://(d|diary).hatena.ne.jp/asin(mobile|diary)?/([^/?]+).*': 'ASIN:$3',
        '^http://(www.)?1470.net/bm/asininfo/([0-9A-Z]+)': 'ASIN:$2',
        '^http://allconsuming.jp/item.cgi\\?asin=([0-9A-Z]+)': 'ASIN:$1',
    };

    // privateなリンク //
    var patPrivate = {
        '^http://127.0.0.1(:[0-9]+)?/(.*)': 'localhost($2)',
        '^http://localhost(:[0-9]+)?/(.*)': 'localhost($2)',
        '^http://192.168.[0-9]+.[0-9]+(:[0-9]+)?/(.*)': 'local IP($2)',
        '^http://(www.)?bloglines.com/myblogs_display\\?(.*)': 'bloglines($2)',
        '^http://www.google.com/reader(/.*)': 'Google Reader($1)',
        '^http://reader.livedoor.com(/.*)': 'LDR($1)',
    };

    // その他のリンク //
    var patOther = {
    };

    var replacePatternList = [
        { category: 'Asin',             pattern: patAsin,      },
        { category: 'Keyword',          pattern: patKeywords,  },
        { category: 'Diary',            pattern: patDiaries,   },
        { category: 'Antenna',          pattern: patAntennae,  },
        { category: 'Bookmark',         pattern: patBookmarks, },
        { category: 'RSS',              pattern: patRSS,       },
        { category: 'Idea',             pattern: patIdeas,     },
        { category: 'Search',           pattern: patSearches,  },
        { category: 'Others(Hatena)',   pattern: patHatena,    },
        { category: 'Others',           pattern: patOther,     },
        { category: 'Private',          pattern: patPrivate,   },
    ];

    /*===== 設定終わり =====*/

    function getNodesFromXPath(aXPath, aContextNode) {
        const XULNS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
        const XHTMLNS = 'http://www.w3.org/1999/xhtml';
        const XLinkNS = 'http://www.w3.org/1999/xlink';

        if (aXPath) {
            aXPath = String(aXPath);
        } else {
            throw 'ERROR: blank XPath expression';
        }
        if (aContextNode) {
            try {
                if (!(gContextNode instanceof Node))
                    throw '';
            } catch(e) {
                throw 'ERROR: invalid context node';
            }
        }

        const type = XPathResult.ORDERED_NODE_ITERATOR_TYPE;
        const xmlDoc  = aContextNode ? aContextNode.ownerDocument : document ;
        const context = aContextNode || xmlDoc.documentElement;
        const resolver = {
            lookupNamespaceURI : function(aPrefix) {
                switch (aPrefix) {
                    case 'xul':
                        return XULNS;
                    case 'html':
                    case 'xhtml':
                        return XHTMLNS;
                    case 'xlink':
                        return XLinkNS;
                    default:
                        return '';
                }
            }
        };

        try {
            var expression = xmlDoc.createExpression(aXPath, resolver);
            return expression.evaluate(context, type, null);
        } catch(e) {
            return {
                snapshotLength : 0,
                snapshotItem : function() { return null; }
            };
        }
    }

    function $X(aXPath, aContextNode) {
        var result = getNodesFromXPath(aXPath, aContextNode);
        switch (result.resultType) {
            case XPathResult.STRING_TYPE : return result.stringValue;
            case XPathResult.NUMBER_TYPE : return result.numberValue;
            case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
            case XPathResult.ORDERED_NODE_ITERATOR_TYPE: {
                var ret = [];
                var node;
                while (node = result.iterateNext()) {
                    ret.push(node);
                }
                return ret;
            }
            case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: {
                var ret = [];
                for (var i = 0, len = result.snapshotLength; i < len ; i++) {
                    ret.push(result.snapshotItem(i));
                }
                return ret;
            }
        }
    }

    function setTitleAsync(liItem, url, func) {
        var text = document.createTextNode('');
        liItem.appendChild(text);
        GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function(responseDetails) {
                    var title = func(responseDetails);
                    if (title) {
                        text.textContent = ' - ' + title;
                    }
                }
            });
    }

    var xpathRefList = $X('//div[contains(@id, "referer-list")]/ul');
    if (xpathRefList.length == 0) return;

    // refererItemHref[アンカーテキスト] = [リンク先1, リンク先2, ...]
    var refererItemHref  = new Object();

    // refererItemCount[カテゴリ][アンカーテキスト]
    var refererItemCount = new Object();

    var ulList = new Object();

    refererlist:
    for (var ul_idx = 0, ul_len = xpathRefList.length; ul_idx < ul_len; ul_idx++) {
        // GM_log('ul_idx = ' + ul_idx);
        var ulRefererList = xpathRefList[ul_idx];
        var liRefererItems = ulRefererList.getElementsByTagName('LI');
        if (!liRefererItems.length) {
            GM_log("liRefererItems.length is null");
            continue;
        }

        for (var li_idx = 0, li_len = liRefererItems.length; li_idx < li_len; li_idx++) {
            // GM_log('li_idx = ' + li_idx);
            var refererCount;
            if (liRefererItems[li_idx].textContent.match(/^\s*(\d+)\s+/))
                refererCount = parseInt(RegExp.$1);
            if (!refererCount) {
                GM_log('refererCount is null');
                continue refererlist;
            }

            // リファラへのアンカー
            var aRefererItem = liRefererItems[li_idx].getElementsByTagName('A')[0];
            if (!aRefererItem) {
                // GM_log('aRefererItem is null');
                continue;
            }

            var text = aRefererItem.text;

            var category = 'Others'; // リファラのカテゴリ

            // リストを書き換える
            replace: 
            for (var pat_idx = 0, pat_len = replacePatternList.length; pat_idx < pat_len; pat_idx++) {
                var cat = replacePatternList[pat_idx].category;
                var pattern_list = replacePatternList[pat_idx].pattern;
                for (var patFrom in pattern_list) {
                    if (! pattern_list.hasOwnProperty(patFrom)) continue;
                    var patTo = pattern_list[patFrom];
                    var text2 = text.replace(RegExp(patFrom), patTo);
                    if (text != text2) {
                        text = text2;
                        // text = text2.replace(/\+/g, '&nbsp;');
                        category = cat;
                        break replace;
                    }
                }
            }

            if (!refererItemCount[category])
                refererItemCount[category] = new Object();
            if (!refererItemCount[category][text])
                refererItemCount[category][text] = 0;

            refererItemCount[category][text] += refererCount;

            if (!refererItemHref[text])
                refererItemHref[text] = new Array();
            refererItemHref[text].push(aRefererItem.href);
        }

        var divReferList = ulRefererList.parentNode;
        divReferList.removeChild(ulRefererList);

        for (var cat_idx = 0, cat_len = replacePatternList.length; cat_idx < cat_len; cat_idx++) {
            var cat = replacePatternList[cat_idx].category;
            if (!refererItemCount[cat])
                continue;

            var aAssoc = refererItemCount[cat];
            var arrKeys = new Array();
            for (var key in aAssoc) {
                if (aAssoc.hasOwnProperty(key))
                    arrKeys.push(key);
            }

            arrKeys.sort(function(a, b) {
                    var cmp = aAssoc[b] - aAssoc[a];
                    if (cmp == 0) {
                        if (a < b) cmp = -1;
                        else if (a > b) cmp = 1;
                    }
                    return cmp;
                });
            var keys = arrKeys;

            var h4 = document.createElement('H4');
            h4.appendChild(document.createTextNode(cat));
            divReferList.appendChild(h4);

            var ulRefCategory = document.createElement('UL');
            ulList[cat] = ulRefCategory;
            for (var k = 0; k < keys.length; k++) {
                var liItem = document.createElement('LI');

                var text = document.createTextNode(refererItemCount[cat][keys[k]] + ' ');
                liItem.appendChild(text);

                var a = document.createElement('a');
                a.href = refererItemHref[keys[k]][0];
                a.textContent = keys[k];
                liItem.appendChild(a);

                for (var i = 1; i < refererItemHref[keys[k]].length; i++) {
                    var text = document.createTextNode(' ');
                    liItem.appendChild(text);
                    var a = document.createElement('a');
                    a.href = refererItemHref[keys[k]][i];
                    a.textContent = i;
                    a.title = refererItemHref[keys[k]][i];
                    liItem.appendChild(a);
                }
                ulRefCategory.appendChild(liItem);
            }
            divReferList.appendChild(ulRefCategory);
        }

        break;
    }

    var deleteReferer = $X('//input[@name = "deletereferer"]')[0];
    if (deleteReferer) {
        // GM_log(deleteReferer)
        deleteReferer.parentNode.removeChild(deleteReferer);
    }

    var ulAsin = ulList['Asin'];
    if (ulAsin) {
        var liItems = ulAsin.getElementsByTagName('LI');
        for (var i = 0; i < liItems.length; i++) {
            var liItem = liItems[i];
            var a = liItem.getElementsByTagName('A')[0];
            if (a.textContent.match(/ASIN:(.*)/)) {
                var asin = RegExp.$1;
                var url = 'http://d.hatena.ne.jp/asin/' + asin;
                setTitleAsync(liItem, url, function(responseDetails) {
                        if (responseDetails.responseText.match(/<h1>(.*)<\/h1>/)) {
                            return RegExp.$1;
                        }
                        return null;
                    });
            }
        }
    }

    var categoriesWantTitle = ['Search', 'Bookmark'];
    for (var cat_idx = 0, cat_len = categoriesWantTitle.length; cat_idx < cat_len; cat_idx++) {
        var ulItem = ulList[categoriesWantTitle[cat_idx]];
        if (ulItem) {
            var liItems = ulItem.getElementsByTagName('LI');
            for (var i = 0; i < liItems.length; i++) {
                var liItem = liItems[i];
                var a = liItem.getElementsByTagName('A')[0];
                if (a.textContent.match(/(http:\/\/[^\)]+)/)) {
                    var url = a.href;
                    // var url = RegExp.$1;
                    setTitleAsync(liItem, url, function(responseDetails) {
                            if (responseDetails.responseText.match(/<title>([^<]+)<\/title>/)) {
                                return RegExp.$1;
                            }
                            return '(no title)';
                        });
                }
            }
        }
    }

})();

えーと、これはなんだったかな。確か元はGreasemonkeyではてなダイアリーのリンク元を整形 | weblog | 東京嫉妬スクリプトがベースになっていたはず。かなりいじってるので色々変わってるけど。でも基本的な構造は最初からそんなに変わってないかな。

なんか最近うまく動かなくなっていたので、上のHatenaAsinInfoと一緒に久々にアップデートしてみた。

追記(2007/01/26)

何人かの人にブックマークされてるみたいなので補足しておくと、スクリプト中で使っている$X()とgetNodesFromXPath()という2つの関数は以下のサイトの記述を参考にしている(ただし、両サイトの記述を総合してから改めて2つの関数に分けたので、名前が同じでもリンク先の関数とは直接対応しないことに注意。紛らわしくて申し訳ない)。

これらは一つ上のエントリで「参考にしたページ」として挙げているものだけど、このエントリを単体で表示した場合には表示されないので念のため。