Large

Post Now browsing to Twitter

By azu_re Last update Nov 17, 2012 — Installed 11,512 times.

There are 53 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name Post Now browsing to Twitter
// @namespace http://efcl.info/
// @description Usage: Ctrl + Shift + Enter -> "Now browsing: ****" on Twitter.
// @include http://*
// @include https://*
// @exclude http://twicli.neocat.jp/twicli.html
// @resource css https://raw.github.com/azu/PBNT/master/style.css
// @require https://raw.github.com/azu/usconfig/v1.2.1/usconfig.js
// @resource usconfigcss https://raw.github.com/azu/usconfig/v1.2.1/usconfig.css.template
// @require https://raw.github.com/azu/OAuth-for-Greasemonkey/master/oauth.js
// @require https://raw.github.com/azu/OAuth-for-Greasemonkey/master/sha1.js
// @require https://raw.github.com/azu/OAuth-for-Greasemonkey/master/GMwrap.js
// @run-at  document-end
// @noframes
// ==/UserScript==

(function(){

    // XPath関数
    var XPath = {
        cache : null,
        reset : function(){
            this.cache = Object.create(null);
        },
        get : function(context, expr, type){
            var x = new XPathEvaluator();
            var cache = this.cache, evaluator;
            if (expr in cache){
                evaluator = cache[expr];
            }else{
                evaluator = cache[expr] = x.createExpression(expr, null);
            }
            return evaluator.evaluate(context, type, null);
        },
        has : function(context, expr){
            return this.get(context, expr, XPathResult.BOOLEAN_TYPE).booleanValue;
        },
        first : function(context, expr){
            return this.get(context, expr, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
        },
        last : function(context, expr){
            var all = this.get(context, expr, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
            return all.snapshotItem(all.snapshotLength - 1) || null;
        },
        all : function(context, expr){
            var all = this.get(context, expr, XPathResult.ORDERED_NODE_ITERAATE_TYPE);
            var ret = [];
            for (var i; (i = all.iterateNext()) !== null;){
                ret.push(i);
            }
            return ret;
        }
    };
    XPath.reset();

    // OAuth
    var clientInfo = {
        name : 'PNBT',
        consumerKey : '9zzFsVFm4nLyfF5WXZsbrg',
        consumerSecret : '318LbLBmvECeZZmSUKVzdq8dpazGtMFf2P6hMPUjWU'
    }
    var TWOauth = new TwitterOauth(clientInfo);// OAuth認証オブジェクト初期化
    TWOauth.injectToConfig = function(authorizeURL){
        var iframe = document.getElementById("usconfig_frame");
        var iframeDoc = iframe.contentDocument;
        // buttons_holder
        var section = iframeDoc.createElement("div");
        section.className = "section_header_holder";
        section.setAttribute("style", "font-size: 95%;" +
                "line-height: 120%;" +
                "margin-left: 20%;" +
                "margin-right: 20%;");


        if (TWOauth.isAuthorize()){// OAuth認証済みな時
            var configDiv1 = iframeDoc.createElement("div");
            configDiv1.className = "config_var";
            configDiv1.style.textAlign = "center";
            var text = iframeDoc.createElement("span");
            text.className = "field_label"
            text.innerHTML = "OAuth logined ";
            var resetBt = iframeDoc.createElement("Button");
            resetBt.textContent = "Logout";
            resetBt.addEventListener("click", function(e){
                if (window.confirm("Do you really logout?")){
                    TWOauth.deleteAccessor();
                    Config.remove();
                }
            }, false)
            configDiv1.appendChild(text);
            configDiv1.appendChild(resetBt);
            section.appendChild(configDiv1);
        }else{

            var configDiv1 = iframeDoc.createElement("div");
            configDiv1.className = "config_var";
            XHRloading.removeText(iframeDoc);// delete loading...
            iframeDoc.createElement("div");
            var explaintext = iframeDoc.createElement("p");
            explaintext.innerHTML = ['',
                '<ol>',
                '    <li>Click "Sign in with Twitter" button</li>',
                '    <li>Allow ' + clientInfo.name + ' access</li>',
                '    <li>Copy PIN code</li>',
                '    <li>Input PIN code and Confirm</li>',
                '</ol>'
            ].join("\n");
            configDiv1.appendChild(explaintext);
            var imgOauth = iframeDoc.createElement("img");
            imgOauth.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAAAYCAYAAAD3eW90AAAH30lEQVRoge2b/VNT6RXH87cwlW6d3W5H90UWEATa3e262247ne7UbmednXZW1850dGpZx5e17trRrCO+CyKiKEbCBpAQiIQIBglBCBJeFRABERYoBoQA4Saf/nD33kK4F4hNWjqTHz4zPJxzvufkPl/yPGQmGo1Go4lK1RIhQijRSMb6ua09bIRbP8LqJCpVS8RcEcK275qoVC3vVraEDclc4ewRYfUhm+sdqytsSOYKZ48Iqw/ZXG+XNyqyzd7OwKQHQRAQBIH8niHVXDUkcwVbF+H/G9lcPy2rV2TguYeWcYFt7QIn+kSDHXU9Us1XQjLXcnlJN2tINNwhxVQn/y7+hoXEAltQ/VZCuHRXqr/uzA1irpjC1v+//XrV9l0TlaolxeRQRBBEY0XXiNSMCTiHn7Gztk1my+1G1foUk0M2l1o82WjnFW0Wr391hp+lZbN2fxobLhtJMTn47fnrvJ9dtKT+ixAu3aX0k0tq5Z8z7tSx22RbVif6y1P8YO9xRV5Lzw/ZPErr/xTZXMnFdkUEQWBLy0JzSUekhHt6ln11HaoakrnU4nFZhZS3duLzw9ScgODzc7C2leRiOwDFjwdVa1+UcOmq6Ud/eYp1xy8viOc3P1xWZ3DSw8TsHBOzXtyeacanZ79fz5HR0hOyeQLXoUA2V1JhtSKB5gpkvUPAPCLgnp5R1ZDMpRY/YnMCcKjczibDHT6paOB3t+pJKqwm5ugFYs/lybnrz9yQ/3J/fCyb6AMnefNiEUmF1UQfOMlP0nJ4+UgmUV98w9rD6Wwy3FHsGai70tp1p3X88OBpNhXYSCqs5qVDZ3lFm0VSYTWxV8t46dDZRfqvHs+hb8xNcVMHP/r6PEmF1QDomx4s2+8TSwNbK5zsuC0+I1PHI7ZWONla4eSXptqQzKM032vn8ojef0KeLSHPKj+nV49fYe3X6aw9nK66p9K+a6JStSQaqhRZzlzRNWJcEARVDclcavGTjQ8B0NW5ePnQWd7ILJBjfWNuLt1rJtFQRcxlI9+Ybbg9M0zMzlHU1kXfmJtUi0POvWJvpKrnCYLPT213H/GXihR7ztcNpjbD2U7nd6PEZhcTc9lI85MhjE0PSMi/zcd5ZXQMjbKpYKG+obMfwednctaLa+QZiYYqAAqcbSuaNdFQxQdFd0RDuh6EfJ7A9YZLNzlcUsnQ8ykEvx9HTz8pGXr5OeXWuXA+HcY5OKo6r7TvmqhULQl6qyIrMZd00VfTkMylFn+noIrWUTcAPSNj7Lh2k3Wnr5Ogt8oPNEFv5dP8W/j8fsxd/bxdUMVVVycAB6x1cu6U10tmyyP0rd0AfFFuV+w5XzeY2j3VTQBsL6jgU71ZPMpnvcRdKuK8rR5rd/8i/V8b7zIxM0tFZy9bSu1Bz5qgt/K+oXLRzKGaJ3C93WDB5/eTbr9Pcoae0ckpcutbZB339AznXF3srGpUnVfad01UqpaNOosigebKGhDvXRJ9HtFYF11dqhqSudTiG3UWkvOsXG59xHPvHD6/n3+U2dios8jHx0adhfRaFwB/Kb3LRp2FXWU1orkqHHJuUUunGDMvjAUyXzeY2hS9lalZL5k1jWTVNFLdOwjADkM59b0DHHO0Kuq7PTOUtHUF3U9ic751kWYo55m/vtrQBoChuZMrznYGxidpGhyVdb51PVxyL+fvuyYqVUt8rlmRQHOZRxZf6Ct7B1Xr43PNsrnU4hsuFvJWdjHxuWa2FFYy5fXSNTxGfK75+4fSQXyumUvODgD+pCth/dkb/DmvVNwUS+2i3F1ldxfEApmfG2xtzeMBGnoHaHs6TGqVk/5nE+jrm/HOCfyqoFJRf9wzQ0lL5wv1i881szmvfJFmKOeZv9Y1iyfCxdom0u61caK+nYNVDYo6y+27JipVS1yOSRFBENj98N/m+qBJNNj8dy9BENC1dqtqSOZSi390zUjC0QzW7Esj7kgGkzMzNA2NEpdjEl/M/Q7ickzsqWwAwNLWzc68UpoHh+VNCczdabIBsL/crthzfm6wtefvtTIn+HjmmSbhaik327vxCgL3B4ZV9UcmPdg6H7Nm7/Gg+8XlmHhPZ16kGcp55q93W+sByG9oYe2+NNbsOcaHF/SKOkshmys226hIx8gYNWNL37kOdYsGU9OQzKUWT2/owOf3MzTpYWZOoH98ks9L7cRmG8Xj40GvnJvf9ogZQWBo0kOWQzwm91oci3L/VnEPgK9s9xV7BuoGU/tHo/jfXkXPU2KzjRy+K957rrg6VfVzW7rx+f2MTE0H3S8228hm3a1FmqGcJ3Cd4ezg+ayX2TkfM3M+7E+GFXWWQjZXTFaRIttN4scRJ/rUzbWtXTSXmoZkLrV4TFYRH+bd4rMSGx8X3OatJfLitZms2ZfGmv0nOFfpAOAPhZVLaq8W3rtexm/yLf/zOdTmCVzHZxfz0bcVvJtb+kL6srk2XDCo8vfKewiCeATufijewSS2tQu0jAv0uydU6yVzLdVjpTwZn6TfPcHjsXF8fj/61u6Q6EYIPbK53jyvX5K/llbT/t0/F13mBUGg/9kEv9ffUq2VzLVcj5WQmFnAZ0WV7Cqt5hfXSkKiGSE8yOZ648yNsCGZK5w9Iqw+ZHO9fio3bEjmCmePCKsP2Vzr03LChmSucPaIsPqQzbXuWHbYkMwVzh4RVh8LvgEUIUIo0Wg0mn8B8xp8c6hoOjUAAAAASUVORK5CYII=";
            imgOauth.addEventListener("click", function(){
                GM_openInTab(authorizeURL);
            }, false);
            configDiv1.appendChild(imgOauth);

            var configDiv2 = iframeDoc.createElement("div");
            configDiv2.className = "config_var";
            var textPin = iframeDoc.createElement("span");
            textPin.className = "field_label"
            textPin.innerHTML = "Input PIN code ";
            var inputPin = iframeDoc.createElement("input");
            var submitBt = iframeDoc.createElement("Button");
            submitBt.textContent = "Confirm";
            submitBt.addEventListener("click", function(e){
                XHRloading.createText(iframeDoc);
                TWOauth.getAccessToken(inputPin.value.replace(/\s/g, ""), function(){
                    XHRloading.removeText(iframeDoc);
                    alert("Authorization copplete!\nHave fun!");
                });
            }, false)
            configDiv2.appendChild(textPin);
            configDiv2.appendChild(inputPin);
            configDiv2.appendChild(submitBt);

            section.appendChild(configDiv1);
            section.appendChild(configDiv2);
        }
        var inp = XPath.last(iframeDoc.body, '//div[@class="section"]');
        inp.appendChild(section);
    }
    // ショートカットの設定関数
    // http://d.hatena.ne.jp/jimo1001/20090601/1243782686 を改変
    var ShortcutKey = function(){
        this.list = [];
        this.init();
    }
    ShortcutKey.prototype.keys = {
        8 : 'BS',
        9 : 'TAB',
        10 : 'Enter',
        13 : 'Enter',
        32 : 'SPC',
        27 : 'ESC',
        33 : 'PageUp',
        34 : 'PageDown',
        35 : 'End',
        36 : 'Home',
        37 : 'Left',
        38 : 'Up',
        39 : 'Right',
        40 : 'Down',
        45 : 'Insert',
        46 : 'Delete',
        112 : 'F1',
        113 : 'F2',
        114 : 'F3',
        115 : 'F4',
        116 : 'F5',
        117 : 'F6',
        118 : 'F7',
        119 : 'F8',
        120 : 'F9',
        121 : 'F10',
        122 : 'F11',
        123 : 'F12'
    }
    ShortcutKey.prototype.skeys = {
        8 : 'BS',
        10 : 'Enter',
        13 : 'Enter',
        32 : 'SPC'
    }
    ShortcutKey.prototype.mkeys = {
        'altKey' : 'A',
        'ctrlKey' : 'C',
        'metaKey' : 'M',
        'shiftKey' : 'S'
    }
    ShortcutKey.prototype.init = function(){
        var self = this;
        window.addEventListener('keydown', function(evt){
            var key = self.get(evt);
            self.list.forEach(function(a){
                if (a.key == key && (a.element == evt.target || a.element == evt.view)){
                    // console.log(a.key +"=="+ key);
                    a.func(evt);
                }
            });
        }, false);
    }
    ShortcutKey.prototype.add = function(elm, key, func){
        this.list.push({
            'element' : elm,
            'key' : key,
            'func' : func
        });
    }
    ShortcutKey.prototype.get = function(evt){
        var key = [], k = '';
        for (mk in this.mkeys){
            if (evt[mk]){
                key.push(this.mkeys[mk]);
            }
        }
        if (evt.which){
            k = this.keys[evt.which] || String.fromCharCode(evt.which).toLowerCase();
            key.push(key.length ? '-' + k : k);
        }else if (evt.keyCode){
            k = this.keys[evt.keyCode];
            key.push(key.length ? '-' + k : k);
        }
        return key.join("");
    }
    // ショートカットの定義
    var shortcut = new ShortcutKey();
    Config.define('usc_basic', function(){
                with (this.builder){
                    var shortURL_opt = [
                        'bit.ly',
                        'j.mp',
                        'goo.gl',
                        'is.gd',
                        'tinyurl.com'
                    ];
                    dialog(
                            "Post Now browsing to Twitter Settings",
                            { width : 600, height : 700 },

                            section(
                                    "User options",
                                    "Behavior/keyboard Preference",
                                    grid(
                                            text("Prefix:", 'defaultTag', "Now browsing: ", { size : 20 }), '\n',
                                            checkbox("Use selection quote", 'isSelection', true), '\n',
                                            checkbox("remove utm_* parameter", 'removeUtm', false), '\n',
                                            checkbox("avoid link to @ and #", 'avoidLinktoMeta', false), '\n',
                                            checkbox("avoid link to string like 'example.com'", 'avoidLinkDomainString', false), '\n',
                                            checkbox("Post with Ctrl+Enter", 'PostWithCtrl', false), '\n',
                                            text("ShortcutKey:", 'ShortCutKey', "CS-Enter", { size : 16 })
                                    )
                            ),
                            section(
                                    "Short URL options",
                                    "select used Short URL service",

                                    grid(
                                            select("Short URL Services", 'ShortURL', shortURL_opt, "bit.ly"), '\n',
                                            text("bit.ly Username:", 'bitlyUserName', "remiko"), '\n',
                                            text("bit.ly APIKey :", 'bitlyAPIKey', 'R_fa2240c646c07b2091c6bc6d109089ef', { size : 30 }),
                                            '\n',
                                            text("goo.gl APIKey :", 'googlAPIKey', '', { size : 30 })
                                    )
                            ),
                            section(
                                    "OAuth Authorization",
                                    "Sign in with Twitter"
                            )
                    );
                }
            },
            // options
            {
                saveKey : 'GM_config',
                aftersave : function(){

                },
                afteropen : function(){
                    // ショートカットの入力補助
                    var iframeDoc = this.frame.contentDocument;
                    iframeDoc.getElementById("control_ShortCutKey").addEventListener('keydown', function(evt){
                        evt.preventDefault();
                        this.value = shortcut.get(evt);
                    }, false);
                    // OAuth Setting
                    if (TWOauth.isAuthorize()){
                        TWOauth.injectToConfig();
                    }else{
                        XHRloading.createText(iframeDoc);
                        TWOauth.getRequestToken(TWOauth.injectToConfig);
                    }
                }
            });
    GM_registerMenuCommand('Post Now browsing to Twitter Setting', function(){
        // https://twitter.com/azu_re/statuses/70426009136144384
        Config.open();
    });


    /* config-設定の初期化 */
    var GM_settings = Config.load();
    var defaultTag = GM_settings.defaultTag;// prefix - 何も書かなかった時の接頭辞
    var UseSelection = GM_settings.isSelection;
    var siteAPI = GM_settings.ShortURL;
    const inputFramename = "GM_INPUT_FRAME";
    var message = {
        /*
         必須
         method
         action
         任意
         headers ヘッダーオブジェクト
         noparam trueならばactionのクエリにURLを付けないようにする
         body POSTのdataを設定 $URL$となってる要素はURLに変わる
         response jsonなら内容に従って取り出す
         */
        'goo.gl' : {
            method : "POST",
            action : "https://www.googleapis.com/urlshortener/v1/url"
                    + ((GM_settings.googlAPIKey !== "") ? "?key=" + GM_settings.googlAPIKey : ""),
            headers : {
                "Content-Type" : "application/json"
            },
            noparam : true,
            body : {
                "longUrl" : "$URL$"
            },
            response : "id"
        },
        'bit.ly' : {
            method : "GET",
            action : "http://api.bit.ly/v3/shorten?&format=txt&login="
                    + GM_settings.bitlyUserName + "&apiKey=" + GM_settings.bitlyAPIKey + "&longUrl="
        },
        'j.mp' : {
            method : "GET",
            action : "http://api.j.mp/v3/shorten?&format=txt&login="
                    + GM_settings.bitlyUserName + "&apiKey=" + GM_settings.bitlyAPIKey + "&longUrl="
        },
        'is.gd' : {
            method : "GET",
            action : "http://is.gd/api.php?longurl="
        },
        'tinyurl.com' : {
            method : "GET",
            action : "http://tinyurl.com/api-create.php?url="
        }
    };
    // make ShortURL: url
    function MakeShortURL(){
        this.initialize.apply(this, arguments);
    }

    MakeShortURL.prototype = {
        initialize : function(url){
            this.url = url;
            this.shortAPI = siteAPI;
        },
        // test
        showURL : function(){
            alert(this.url);
        },
        formatParams : function(content){
            var result;
            if (typeof(content) === "object"){
                result = {};
                for (var key in content){
                    if (content[key] === "$URL$"){
                        result[key] = this.url;
                    }else{
                        result[key] = content[key];
                    }
                }
                result = JSON.stringify(result);
            }else{
                result = content;
            }
            return result;
        },
        getShortURL : function(callbackFunc){
            var btnDiv = XPath.first(document, 'id("GM_Now_browsing")');
            var inpuFrame = XPath.first(document, '//iframe[@name="' + inputFramename + '"]');
            if (!btnDiv && !inpuFrame){
                var mes = message[siteAPI];
                var XHRobj = {};
                XHRobj.method = mes.method;
                XHRobj.url = mes.action + ((mes.noparam) ? "" : encodeURIComponent(this.url));
                mes.headers && (XHRobj.headers = mes.headers);
                mes.body && (XHRobj.data = this.formatParams(mes.body));
                XHRobj.onload = function(res){
                    if (res.readyState == 4 && (res.status == 200 || res.status == 201)){
                        clearInterval(timerId);
                        XHRloading.removeDiv();
                        var shortedURL = res.responseText;
                        // 上手く取得できてない場合はkill
                        if (typeof shortedURL === "undefined" || shortedURL === "undefined"){
                            return;
                        }
                        if (mes.response){
                            var resJSON = JSON.parse(shortedURL);
                            shortedURL = getObjValueFromString(resJSON, mes.response);
                        }
                        callbackFunc(shortedURL);
                    }
                }
                XHRobj.onerror = function(e){
                    GM_log(e);
                }
                // ローディング表示
                XHRloading.create();
                var GM_xhr = GM_xmlhttpRequest(XHRobj);
                // タイムアウト
                var self = this;
                var timerId = setTimeout((function(arg){
                    return function(){
                        GM_xhr.abort();
                        var isChanged = self.changeShortAPI();// 短縮URLを変える。
                        XHRloading.removeDiv();
                        if (isChanged){
                            arg.callee.apply(self, arg);
                        }
                    }
                })(arguments), 7000);
            }
        },
        // siteAPIを切り替えていく
        changeShortAPI : function(){
            message[siteAPI].mark = true;
            for (var i in message){
                if (message[i] != siteAPI && !message[i].mark){
                    siteAPI = i;
                    return true;
                }
            }
        }
    }

    /*  ポストメッセージの構造
     Message
     comment
     activity
     quotes
     title
     url
     */
    // post to Twitter : url ,title
    function PostTwitter(){
        this.initialize.apply(this, arguments);
    }

    PostTwitter.prototype = {
        initialize : function(url, title){
            this.url = url;
            this.title = title;
            this.comment = "";
            this.activity = "";
        },
        make_message : function(){
            if (UseSelection){
                var sel = window.getSelection();
                var count = sel.rangeCount;
                var r, t;
                if (count > 1){// 複数の選択範囲→「引用」「引用」"タイトル"
                    var selectionRange = [];
                    for (var i = 0; i < count; i++){
                        if (sel.getRangeAt(i) != ""){// 空は取り除く
                            selectionRange.push(sel.getRangeAt(i));
                        }
                    }
                    if (selectionRange.length > 1){
                        r = selectionRange.join('」「')
                    }else{
                        r = selectionRange;
                    }
                    var selection = r.toString().trim();
                    if (selection.length > 1){
                        t = [' 「', selection, '」 ', ' "', this.title, '" '].join('');
                    }else{
                        t = [' "', this.title, '" '].join('');
                    }
                }else if (count == 1){// 単一の選択範囲→「引用」"タイトル"
                    var selection = sel.toString().trim();
                    if (selection.length > 1){
                        t = [' 「', selection, '」 ', ' "', this.title, '" '].join('');
                    }else{
                        t = [' "', this.title, '" '].join('');
                    }
                }else{// 選択範囲なし→"タイトル"
                    t = [' "', this.title, '" '].join('');
                }
            }
            this.activity = t;

            // 渡すオブジェクト
            var defObj = {
                activity : this.activity,
                url : this.url
            }
            var self = this;
            makeFrame(function gotFrame1(iframe, win, doc){
                self.doc = doc;
                self.iframe = iframe;
                iframe.width = "100%";
                iframe.style.width = "100%";
                iframe.style.position = "fixed";
                var inputUI = self.createHTML(defObj);
                self.addCSS(doc);
                // 入力領域を表示
                doc.body.appendChild(inputUI);
                self.addInputEvent();
            }, inputFramename);

        },
        // 入力領域のイベントを追加
        addInputEvent : function(){
            var self = this;
            var doc = self.doc;
            var inputHTML = XPath.first(doc, 'id("GM_Now_Box")');
            var inputFiled = XPath.first(inputHTML, 'id("GM_Now_InputField")');
            var activityFiled = XPath.first(inputHTML, 'id("GM_Now_SubActivity")');
            var counterBox = XPath.first(inputHTML, 'id("GM_Now_SubCounter")');
            var counter = parseInt(counterBox.textContent, 10); // 引用 + タイトル + URLの文字数
            counterBox.textContent = counter + strLen(activityFiled.textContent);// デフォルトの表示
            // bodyにもESCキーが聞くように
            document.addEventListener("keypress", function(e){
                var esc = (e.keyCode == 27);
                if (esc){
                    this.removeEventListener("keypress", arguments.callee, false);
                    document.body.removeChild(self.iframe);
                }
            }, false)
            // フォーカスをinputFiledへ移す
            inputFiled.focus();
            inputFiled.addEventListener("keypress", function(evt){
                var shortcutFlag = false;
                if (GM_settings.PostWithCtrl){// Enterでポスト
                    var c = (evt.ctrlKey || evt.metaKey);
                    var v = (evt.keyCode == 13);
                    shortcutFlag = c && v;
                }else{
                    shortcutFlag = (evt.keyCode == 13);
                }
                var esc = (evt.keyCode == 27);
                if (shortcutFlag){
                    this.removeEventListener("keypress", arguments.callee, false);
                    self.comment = inputFiled.value;
                    self.activity = activityFiled.textContent;
                    document.body.removeChild(self.iframe);
                    focusBody();
                    self.arrangeMes();
                }else if (esc){ // Escでキャンセル
                    this.removeEventListener("keypress", arguments.callee, false);
                    document.body.removeChild(self.iframe);
                    focusBody();
                }else if (evt.keyCode == 9){// TabキーでactivityFiledを有効化して移動
                    evt.preventDefault()
                    activityFiled.setAttribute("contenteditable", "true");
                    activityFiled.focus();
                }
            }, false);
            // contenteditableの日本語バグ?回避  - https://twitter.com/azu_re/statuses/16587183821
            inputFiled.addEventListener("focus", function(e){
                activityFiled.setAttribute("contenteditable", "false");
            }, false);
            activityFiled.addEventListener("click", function(e){
                activityFiled.setAttribute("contenteditable", "true");
            }, false);
            // 文字数カウント
            inputFiled.addEventListener("keyup", function(e){
                counterBox.textContent = counter + strLen(activityFiled.textContent) + strLen(inputFiled.value);
            }, false);
        },
        // プロンプトのHTML生成
        createHTML : function(obj){
            if (!obj){
                obj = {};
            }
            var defVal = {
                activity : obj.activity || "",
                url : obj.url || ""
            }
            var counter = strLen(obj.url);

            // バグ - https://twitter.com/azu_re/statuses/16219838145
            var E4Xhtml = ['',
                '<div style="display: block;" id="GM_Now_Box" class="GM_Now_ThemeDefault">',
                '    <input type="text" value="" id="GM_Now_InputField" class="GM_Now_ThemeDefault" />',
                '    <div id="GM_Now_Sub">',
                '        <div class="GM_edit_guard">',
                '            <div contenteditable="false" id="GM_Now_SubActivity">' + defVal.activity + '</div>',
                '        </div>',
                '        <div id="GM_Now_SubCounter" style="font-size: 25px;">' + counter + '</div>',
                '        <div id="GM_Now_SubURL">' + defVal.url + '</div>',
                '    </div>',
                '</div>',
                ''].join("\n");
            var htmlForPost = e4xToDOM(E4Xhtml);
            return htmlForPost;
        },
        addCSS : function(doc){
            // based on gleebox
            addCSS(doc, GM_getResourceText("css"));
        },
        arrangeMes : function(){
            if (this.comment == null){
                return;
            }
            if (this.comment == ''){
                this.comment = defaultTag;
            }
            this.activity = preventAutoLink(this.activity);
            this.comment = preventAutoLink(this.comment);
            var allStr = [this.comment, this.activity, this.url].join(' ');
            var l = strLen(this.activity.length) || true;
            var cutlength;
            if (l && strLen(allStr) > 140){
                // 現在の全体 - (140 - this.comment)
                var entStrLength = 2;// 削った後に末尾に付ける文字数
                cutlength = (strLen(allStr) - 140) + entStrLength;
                if (cutlength > 0){
                    this.activity = this.activity.slice(0, strLen(this.activity) - cutlength);
                    if (UseSelection && cutlength > strLen(this.title)){
                        // 選択範囲を削る
                        this.activity += '…」';
                    }else{
                        // タイトルを削る
                        this.activity += '…" '
                    }
                    allStr = [this.comment, this.activity, this.url].join('');
                }
            }
            // ポストメッセージの完成
            this.post_message = allStr;
            this.post();
        },
        post : function(){
            var content = {status : this.post_message, source : clientInfo.name};
            TWOauth.post('https://api.twitter.com/1/statuses/update.json', content, function(evt){

            });
        }
    }
    // iframeから元のbodyにフォーカスを戻す
    // bodyに直接フォーカスできないので、適当なボタンを作ってフォーカスさせる。
    function focusBody(){
        var inf = document.createElement("button");
        inf.setAttribute("style", "position:fixed; top:4px; left:4px; width:1px; height:1px; border:none; background-color:#55f; opacity:0;");//-moz-opacityは3.5で廃止
        document.body.appendChild(inf);
        inf.focus();
        document.body.removeChild(inf);
    }

    /*  CSSをcontextに加える
     how to use
     addCSS(document ,
     *{
     font-size:12px;
     background-color:#000;
     }
     ]]></>);
     */
    function addCSS(context, css){
        if (!context){
            context = document;
        }
        var sheet = context.createElement('style');
        sheet.type = 'text/css';
        var _root = context.getElementsByTagName('head')[0] || context.documentElement;
        sheet.textContent = css;
        return _root.appendChild(sheet).sheet;

    }

    // フレームパネルの作成
    function makeFrame(callback/*(iframeTag, window, document)*/, name){
        function testInvasion(){
            iframe.removeEventListener("load", done, true);
            var message = ((new Date) - load.start) + "ms passed, ";
            try{ // probe for security violation error, in case mozilla struck a bug
                var url = unsafeWindow.frames[framename].location.href;
                message += url == "about:blank" ? "but we got the right document." : "and we incorrectly loaded " + url;
                done();
            }
            catch (e){
                document.body.removeChild(iframe);
                makeFrame(callback, name);
            }
        }

        function done(){
            clearTimeout(load.timeout);
            var win = unsafeWindow.frames[framename];
            var doc = iframe.contentWindow.document;
            callback(iframe, win, doc);
        }

        var iframe = document.createElement("iframe");

        var framename = iframe.name = typeof name !== "undefined" ? name : ("pane" + (makeFrame.id = (makeFrame.id || 0) - 1));
        iframe.setAttribute("style", "overflow:auto;z-index:7890; border:0; margin:0; padding:0;top:82%; bottom:0; left:0;");
        iframe.src = "about:blank";
        iframe.addEventListener("load", done, true);
        var frames = makeFrame.data || {};
        var load = frames[framename] || {
            start : new Date,
            sleepFor : 400
        };
        load.timeout = setTimeout(testInvasion, load.sleepFor);
        load.sleepFor *= 1.5;
        frames[framename] = load;
        makeFrame.data = frames;
        document.body.appendChild(iframe);
    }

    // ローディング■の操作
    var XHRloading = {
        create : function(){
            var btnDiv = XPath.first(document, 'id("GM_Now_browsing")');
            if (!btnDiv){
                var btn = document.createElement('div');
                btn.setAttribute("style", 'background: rgb(255, 0, 0) none repeat scroll 0% 0%; font-size: 12px; position: fixed;overflow:auto; bottom: 3px; right: 3px; -moz-background-clip: border; -moz-background-origin: padding; -moz-background-inline-policy: continuous; color: rgb(255, 255, 255); width: 20px; height: 20px; z-index: 255;');
                btn.id = "GM_Now_browsing";
                document.body.appendChild(btn);
            }
        },
        removeDiv : function(){
            var btn = XPath.first(document, 'id("GM_Now_browsing")');
            if (btn){
                btn.parentNode.removeChild(btn);
            }
        },
        createText : function(_doc){
            var txt = XPath.first(_doc, 'id("loading_message")');
            if (!txt){
                var loading = _doc.createElement("p");
                loading.setAttribute("style", ['',
                    'position:fixed;',
                    'right:0;',
                    'top:80%;',
                    'color:#fff;',
                    'background:#000;'
                ].join("\n"));
                loading.innerHTML = "Now Loading...";
                loading.id = "loading_message";
                _doc.body.appendChild(loading);
            }
        },

        removeText : function(_doc){
            var txt = XPath.first(_doc, 'id("loading_message")');
            if (txt){
                _doc.body.removeChild(txt);
            }
        }
    }

    // 文字列を元にJSONオブジェクトの値を取り出す
    function getObjValueFromString(obj, str){
        var req = str.split(".");
        for (var i = 0, len = req.length; i < len; i++){
            obj = obj[req[i]];
        }
        return obj;
    }

    // E4X to DOM
    function e4xToDOM(html){
        var range = document.createRange();
        var htmlDOM = range.createContextualFragment(html);
        return htmlDOM;
    }

    // http://liosk.blog103.fc2.com/blog-entry-162.html
    function strLen(str){
        var i = 0, len = str.length, result = 0;
        while (i < len){
            result++;
            var x = str.charCodeAt(i++);
            if (0xD800 <= x && x < 0xDC00){
                i++;
            }
        }
        return result;
    }

    // debug関数
    function log(m){
        if (unsafeWindow.console){
            unsafeWindow.console.log(m);
        }else{
            console.log(m); //GM_log(m)でも同じ。
        }
    }

    // ショートカットのイベント設定
    shortcut.add(window, GM_settings.ShortCutKey, function(evt){
        evt.preventDefault();
        launchPNBT();
    });
    GM_registerMenuCommand("Post to Twitter", function(){
        launchPNBT();
    })
    function launchPNBT(){
        if (!TWOauth.isAuthorize()){
            alert("You must Sign-in with Twitter");
            Config.open();
            return;
        }
        var normalURL = window.location.href;
        var title = (document.title) ? document.title : window.parent.document.title;
        if (/(^http:\/\/reader\.livedoor\.com\/|^http:\/\/fastladder\.com\/reader\/)/.test(normalURL)){
            var w = unsafeWindow;
            var item = w.get_active_item(true);
            var feed = w.get_active_feed();
            normalURL = item.link;
            title = item.title + " - " + feed.channel.title;
        }
        // utm_*を取り除く
        if (GM_settings.removeUtm){
            normalURL = normalURL.replace(/([\?\&]utm_(source|medium|campaign|content)=.+)/ig, '');
        }
        // @や#を取り除く
        if (GM_settings.avoidLinktoMeta){
            title = removeMeta(title);
        }
        var receivedShortUrl = new MakeShortURL(normalURL);// 初期化
        receivedShortUrl.getShortURL(function(shortedURL){
            var twitterNew = new PostTwitter(shortedURL, title)
            twitterNew.make_message();
        });
    }

    // ユーザー名やハッシュタグのリンクをさせないようにゼロ幅文字を挟む
    function removeMeta(str){
        var reg = {
            'userName' : /\B([@@])([a-zA-Z0-9_]{1,20})\b/g,
            'hashTag' : /\B([##])([a-zA-Z0-9_]+)/g
        };
        for (var i in reg){
            str = str.replace(reg[i], "$1‌$2");// $1 ゼロ幅文字 $2
        }
        return str;
    }

    function preventAutoLink(str){
        // 設定によってはそのまま返す
        if (!GM_settings.avoidLinkDomainString){
            return str;
        }
        var MAGIC_NUMBER = "0xe38286e381ae";
        var URLReg = /(https?:\/\/[^:/<>&\s]+(?::\d+)?(?:\/[^#\s<>&()"']*(?:#(?:[^\s<>&"'()]+))?)?)|(.)/ig;
        var domainReg = /(?:[a-z][a-z0-9-]*[a-z0-9](\.))+(?:jp|aero|biz|com|coop|info|museum|name|net|org|pro|jobs|travel|arpa|edu|gov|int|mil|nato)/ig;
        var urlStack = [];
        var result = "";
        // http:// なURLを保護
        result = str.replace(URLReg,function(m, url){
            if (url){
                m = m.replace(url, MAGIC_NUMBER);
                urlStack.push(url);
            }
            return m;
            // ドメイン文字列を自動リンクされないように回避
        }).replace(domainReg,function(m, domain){
                    if (domain){
                        m = m.replace(".", "-", "g");
                    }
                    return m;
                    // 保護したURLを戻す
                }).replace(MAGIC_NUMBER, function(m){
                    return urlStack.shift();
                }, "g");
        return result;
    }
})();