短縮URLを展開するグリモン書いたよー

バグの修正について、末尾に追記しました。

短縮URLってなかなか怖いですね。

字数制限のキツいサイトでURLを貼るとき、よく使われる短縮URLtinyURLとか、RubyURLとか。twitterなんかでよく見ますね。たいてい素敵なサイトの紹介ですので、ホイホイ踏んじゃいます。そのノリで、怪しいサイトでもクセで踏んじゃいがちです。転送先がsuper-burakura.com*1なんてどう見てもデンジャラスなサイトかもしれないのにです!

転送先のURLを事前に確認する方法はないの?

API叩けばいいとかいうけど、たくさんある短縮URLサービスのリファレンスなんて見てらんないんです!他にもいっぱいあるでしょ、短縮URLサービスなんて。ブラウザでURLクリックすればリダイレクトされる、って機能は一緒なのに、なんで面倒なことしなきゃいけないの!俺は面倒なことが嫌いなんだ!
リダイレクト教えてくれるアドオンとかないの?とか思ったんだけど、見つけられませんでした。その代わり、こんなグリモンをみつけました(http://d.hatena.ne.jp/Constellation/20080613/1213374738)。リダイレクト先のURLをゲットできるんですね。じゃあ簡単そうだ。
俺も俺なりに書いてみたよ。

// ==UserScript==
// @name           Expand short URL
// @namespace      http://d.hatena.ne.jp/xenoma/
// @description    show tooltip onshort URL and replace href and HTML
// @include        http://*
// @include        https://*
// @author         xenoma
// ==/UserScript==
(function(){
    
    var FLAG = {
        'replaceTiming': 0,    // 0:リンククリック時 1:読み込み時
        'replaceType': 0    // 0:短縮URLを残す 1:短縮URL自体を置換
    };
    
    //短縮URLサービスのドメインをどんどん追加しよう
      var LIST = [
        /* tinyURL */
        'tinyurl\\.com',
        /* mooo.jp */
        'mooo\\.jp',
        /* mo-v.jp */
        'mo-v\\.jp',
        /* jpan.jp */
        'jpan\\.jp',
        /* snipurl.com */
        'snipurl\\.com',
        /* qqa.jp */
        'qqa\\.jp',
        /* www.qurl.com */
        'qurl\\.com',
        /* rubyURL */
        'rubyurl\\.com',
        /* PURL */
        'zz\\.tc',
        /*is.gd */
        'is\\.gd',
      ];
    var r =LIST.join("|");
    var rx = new RegExp('^https?:\\/\\/([\\w]+\\.)*(' + r + ')');

    // 短縮URL探します
    var ExpandShortUrl = function(){
        var links = document.evaluate('//a', document, null, 
            XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        for(var i = 0; i < links.snapshotLength; i++){
            var link = links.snapshotItem(i);
            if(link.href && rx.test(link)){
                switch(FLAG['replaceTiming']){
                case 0: // クリック時置換
                    (function(){
                        var c = checkLink.cloneNode(true);
                        var l = (function(_l){return _l;})(link);
                        c.addEventListener('click', 
                            function(){
                                c.removeEventListener('click', arguments.callee,false);
                                c.innerHTML = 'Wait...';
                                getUrl(l, c);
                            },
                            false);
                        link.parentNode.insertBefore( c, l);
                    })();
                    break;
                case 1: // 即時置換
                    var l = (function(_l){return _l;})(link);
                    getUrl(l, null);
                    break;
                }
            }
        }
    }
    
    // CHECK 雛形
    var checkLink = document.createElement('a');
    checkLink.style.cssText="background : rgb(187, 0, 0) none repeat scroll 0% 0%;"
        + "color : rgb(255, 255, 255);"
        + "margin-right: 2px;";
    checkLink.href = "javascript:void(0)";
    checkLink.innerHTML = 'CHECK';
    
    
    // 短縮前URLゲット 
    var getUrl = function(link, cLink){
        var opt = {
            method:'get',
            url:link.href,
            link:link,
            onload:function({finalUrl:url}){
                if(cLink){
                    switch(FLAG['replaceType']){
                    case 0:
                        replaceUrl(cLink, url)
                        break;
                    case 1:
                        cLink.style.cssText="display:none;";
                        replaceUrl(link, url);
                        break;
                    }
                }else{
                    replaceUrl(link, url);
                }
            }
        }
        setTimeout(GM_xmlhttpRequest, 0, opt)
    }
    
    // 書き換え
    var replaceUrl = function(element, url){
        element.setAttribute('title', url);
        element.setAttribute('href', url);
        element.innerHTML = url;
    }
    
    ExpandShortUrl();

})();

入れるとどうなるの

入れる前

入れた後

CHECKをクリックしたら

短縮URLサービスを見つけたら、LISTに追加すればいいだけなので、簡単です。ドメインに含まれる"."を"\\."って置き換えるのがちょっと面倒だけど、それだけです。
ソースの上手な書き方とか、パフォーマンスとかよくわかんないけど、いい感じに動いたよ!Greasemonkey大好き!
使いたいとか直したいとかいう方がいらっしゃいましたら、どうぞご利用ください。

追記

http://hoge.zz.tc/fugaのようにサブドメインが入ったときに動かないバグがありました。40行目を

    var rx = new RegExp('^https?:\/\/([\w]+\.)*(' + r + ')');

から

    var rx = new RegExp('^https?:\\/\\/([\\w]+\\.)*(' + r + ')');

と修正しました。現在表示されているコードは修正済みです。

*1:架空のドメインだよ

はまちちゃん先生のはしーさーふ、ってヤツなんですね。

はまちちゃん先生のはしーさーふ、ってヤツなんですね。

今回のお話とは全然関係ないじゃん!全然クロスってないもん!一人相撲ですね><
ShareTabsだって、よくある短縮URLサービスと対して変わんない。JavaScriptが実行できたって、攻撃先がなかったらなんてことはないですね。TinyURLで作られたURL踏んだらブラクラサイトでした><ぐらいのもんですね。
短縮URLを踏んだら超自動的におかしな振る舞いをします!、っていう点ではみんな仲間。ShareTabsだけを悪く言うのは良くありませんね。
今回の件では、短縮URLはみんな危険です!って思うことにします。