backup Hatena Space Fork

  • /*
     * @title backup Hatena Space
     * @description backup Hatena Space. see http://a-kuma3.hatenablog.com/entry/hatena_space_backup
     * @include http://space.hatena.ne.jp/*
     * @license MIT License
     */
    (function(){
        /*
            [OPTIONS]
    
            only_my_entry:
                remove other users posts.
                need login.
    
            expand_hatena_star:
                slower 10-50% to exract.
                larger 10% or more.
    
            embed_stylesheet:
                need 218 KB more.
        */
        var opt = {
                only_my_entry: false,
                expand_hatena_star: false,
                embed_stylesheet: true,
            };
    
    
    
        // check location
        re_check = new RegExp("^http://space\.hatena\.ne\.jp/~/\\d+/\\d+#?$");
        if (! re_check.test(location.href)) {
            console.log('"' + location.href + '"');
            alert("use at topics page @ space.hatena.ne.jp.");
            return;
        }
    
        var hspace_pages = [];
        var hspace_stylesheet = "";
        var hspace_title = "";
    
    
        var need_break = false;
        var is_last_page = false;
        var page = 1;
        var max_page = -1;
        var last_entry_number = -1;
        var d_ = document;
        var b_ = d_.body;
        var style_url = "http://space.hatena.ne.jp/css/master.css?47ab9942";
        var t_start = new Date();
    
        function setStyle(target, prop) {
            for (k in prop) {
                target.style[k] = prop[k];
            }
        }
    
        function removeNode(e) {
            e.parentNode.removeChild(e);
        }
    
        function $id(id) {
            return d_.getElementById(id);
        }
    
        function createProgressView() {
            if ($id("progress")) {
                removeNode($id("progress").parentNode);
            }
            var panel = d_.createElement("DIV");
            setStyle(panel, {
                    display: "inline-block",
                    position: "fixed",
                    top: "1ex",
                    right: "1ex",
                    backgroundColor: "green",
                    border: "2px inset darkgreen",
                    color: "white",
                    padding: "0.5ex 2ex",
                    zIndex: 1000,
                });
    
            var m = d_.createElement("DIV");
            m.id = "progress";
            m.innerHTML = " ";
            panel.appendChild(m);
    
            var btn = d_.createElement("BUTTON");
            btn.innerHTML = "BREAK";
            btn.style.marginTop = "0.5em";
            btn.onclick = function() {
                need_break = true;
            };
    
            panel.appendChild(btn);
            b_.appendChild(panel);
        }
    
    
        var xhr = new XMLHttpRequest();
        xhr.onload = function(e) {
            if (e.target.status <= 200) {
                treatResponse(e.target.response);
            }
        };
    
        function is_spam(entry) {
            /*
                TODO:
                another spam pattern
            */
            var re = new RegExp("^https?://\\S+$");
            if (re.test(entry.textContent)) {
                return true;
            }
    
            return false;
        }
    
        function treatResponse(resp) {
            hspace_title = resp.querySelector("head title").innerHTML;
            var entries = resp.querySelector("div#entries");
    
            if (max_page == -1) {
                var last_entry = entries.querySelector("div.entry");
                last_entry_number = parseInt(last_entry.dataset.entryNumber, 10);
                max_page = Math.ceil(last_entry_number / 20.0);
            }
    
            // remove entries of other users
            if (opt.only_my_entry) {
                try {
                    var myid = resp.documentElement.dataset.userName;
                    var re = new RegExp("/" + myid + "/$");
                    Array.from(entries.querySelectorAll("div.entry div.author > a[href]")).forEach(function(e) {
                        if (! re.test(e.href)) {
                            removeNode(e.parentNode.parentNode.parentNode);
                        }
                    });
                } catch (ex) {
                    console.error("ERROR: " + e.target.responseURL);
                    console.error(ex);
                }
            }
    
            // remove spam entries
            Array.from(entries.querySelectorAll("div.entry > div.body > div.plain")).forEach(function(e) {
                if (is_spam(e)) {
                    console.log("maybe spam: " + e.parentNode.parentNode.querySelector("div.author").textContent.replace(/\s/g, ""));
                    removeNode(e.parentNode.parentNode);
                }
            });
    
            // change user link space to profile of user
            {
                var sel = [
                    "a.author-image-link",
                    "div.meta > .author > a",
                    "ul.comment-list > li > a",
                    "ul.comment-list > li > .user-comment > a",
                    "a.id-call",
                ].join(",");
                Array.from(entries.querySelectorAll(sel)).forEach(function(e) {
                    e.href = e.href.replace(/space\.hatena\.ne\.jp/, "profile.hatena.ne.jp");
                });
            }
    
            // display all comments
            Array.from(entries.querySelectorAll("div.comments > ul.comment-list li.hide")).forEach(function(e) {
                e.classList.remove("hide");
            });
    
            // remove node (reduce size)
            {
                var sel = [
                    // post form
                    "div.comments > form.post-comment",
                    "div.reply-action > form.quick-reply",
    
                    // action element
                    "div.comments > a.expand-comments",
                    "ul.comment-list li div.comment-data > a.delete-comment",
                    "div.reply-action > a.open-entry-menu",
                    "div.reply-action > ul.entry-menu",
    
                    // star button
                    "div.reply-action button.star-add-button",
                ].join(",");
                Array.from(entries.querySelectorAll(sel)).forEach(function(e) {
                    removeNode(e);
                });
            }
    
            // Hatena Star
            if (opt.expand_hatena_star) {
                var keys = [];
                Array.from(entries.querySelectorAll(".star-list-container")).forEach(function(e) {
                    keys.push(e.dataset.resourceKey);
                });
                var url = "http://space.hatena.ne.jp/-/api/star?" + keys.map(function(e) {
                    return "resource_key[]=" + e;
                }).join("&");
                var xx = new XMLHttpRequest();
                xx.open('GET', url, true);
                xx.onload = (function() {
                    var ec = entries;
                    return function(ev) {
                        var a;
                        eval("a=" + ev.target.responseText);
                        a.resources.forEach(function(e) {
    //console.log(e);
                            e.stars.forEach(function(ee) {
                                var stcn = ec.querySelector('span.star-list-container[data-resource-key="' + ee.resource_key + '"]');
                                var box = d_.createElement("span");
                                box.className = "star-box";
                                box.style.backgroundImage="url(" + ee.user.profile_image + ")";
                                var img = d_.createElement("a");
                                img.className = "star-image";
                                img.href = "http://s.hatena.ne.jp" + ee.user.path;
                                img.innerHTML = ee.user.name;
                                box.appendChild(img);
                                stcn.appendChild(box);
                            });
                        });
                        if (is_last_page) {
                            finishLoading();
                        }
                    };
                })();
                xx.send(null);
            } else {
                var sel = [
                    "div.reply-action div.star-container",
                ].join(",");
                Array.from(entries.querySelectorAll(sel)).forEach(function(e) {
                    removeNode(e);
                });
            }
    
            // post time
            Array.from(entries.querySelectorAll("time")).forEach(function(e) {
                function d2(n) {
                    return n < 10 ? "0"+n : n;
                }
                var t = new Date(parseInt(e.dataset.epochMilliseconds));
                e.innerHTML = t.getFullYear() + "/" + d2(t.getMonth()+1) + "/" + d2(t.getDate()) + " " +
                    d2(t.getHours()) + ":" + d2(t.getMinutes()) + ":" + d2(t.getSeconds());
            });
    
    
            hspace_pages.push(entries);
    
            if (need_break) {
                $id("progress").innerHTML = "BREAK Loading !";
                return;
            }
    
            is_last_page = !resp.querySelector("#timeline-pager-loading");
            if (is_last_page) {
                $id("progress").innerHTML = "FINISH Loading";
                if (! opt.expand_hatena_star) {
                    setTimeout(finishLoading, 0);
                }
            } else {
                page += 1;
                loadEntries();
            }
        }
    
        function finishLoading() {
            console.log("*** FINISH ***");
            displayResultView();
            console.log("display initialized");
            displaySource();
            console.log("source displayed");
    
            var lapse = (new Date().getTime() - t_start.getTime()) / 1000;
            console.log("=== COMPLETE ===  " + [
                lapse + " sec",
                max_page + " pages",
                last_entry_number + " posts",
                ].join(", "));
        }
    
        function displayResultView() {
            b_.innerHTML = "";
            b_.classList.remove("new-space");
    
            // source area
            var sourceArea = d_.createElement("div");
            setStyle(sourceArea, {
                display: "inline-block",
                width: "20em", 
                padding: "0.5ex 2ex",
                backgroundColor: "palegoldenrod",
                verticalAlign: "top",
            });
    
            function createUnitArea(dest, title, id, viewtype) {
                var e_txt = d_.createElement("textarea");
                e_txt.id = "source-" + id;
                e_txt.rows = 5;
                setStyle(e_txt, {
                    width: "100%",
                    display: "block",
                });
    
                var e_title = d_.createElement("a");
                e_title.innerHTML = title;
                e_title.href = "#";
                setStyle(e_title, {
                    display: "block",
                    textDecoration: "underline",
                    borderLeft: "1em solid saddlebrown",
                    paddingLeft: "1ex",
                    marginTop: "0.5ex",
                });
                e_title.onclick = function() {
                    var viewArea = $id("view-area");
                    var dd_ = viewArea.contentWindow.document;
                    dd_.open(viewtype, "replace");
    //              dd_.charset = "Shift_JIS";
                    dd_.write(this.nextSibling.value);
                    dd_.close();
                    return false;
                };
    
                dest.appendChild(e_title);
                dest.appendChild(e_txt);
            }
            createUnitArea(sourceArea, "entries HTML"   , "entries"   , "text/html");
            createUnitArea(sourceArea, "style sheet"    , "stylesheet", "text/plain");
            createUnitArea(sourceArea, "images URL list", "imagelist" , "text/plain");
            createUnitArea(sourceArea, "images HTML"    , "imagepage" , "text/html");
            b_.appendChild(sourceArea);
    
            // preview frame
            var viewArea = d_.createElement("iframe");
            viewArea.id = "view-area";
            setStyle(viewArea, {
                width: "600px",
                height: window.innerHeight + "px",
                display: "inline-block",
                verticalAlign: "top",
            });
            b_.appendChild(viewArea);
        }
    
        function code_stylesheet() {
            var code = [
                '<!-- need replace -->',
                '<link rel="stylesheet" href="' + style_url + '" />',
                ].join("\n");
            if (opt.embed_stylesheet) {
                code = [
                '<style>',
                hspace_stylesheet,
                '</style>',
                ].join("\n");
            }
            var star_image = [
                "<style>",
                ".star-image {",
                "background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAABGdBTUEAALGPC/xhBQAAAh9QTFRF1dXVlZWV3d3dxcXFyMjI/v7+9PT04+PjZGRk/f399fX1+/v76urqzc3N/f396enp/v7+/Pz8/v7+xMTE/v7+YWFhSUlJ4uLi+Pj4xcXF7+/vmJiYBgYG/Pz8AAAAtbW1+fn56Ojo3t7e/f39/v7+OTk57u7uq6urjY2NbGxs+/v7IyMj/Pz8+fn59vb2/f39v7+/9/f30tLSwcHB8fHx4uLij4+P1dXV9fX1urq6+fn5i4uL9vb24eHh+fn57u7uwcHB6+vrwcHB6enpX19fUlJS7u7udHR09PT07+/v+Pj4Ly8v5+fnycnJ/Pz81dXV6Ojo/v7++vr6+fn5sbGx8/PzFhYWpaWl39/fBwcH3t7es7OzycnJzc3N1dXV6enp/v7+zs7OcHBw8/Pz/f39/Pz8/f39/f39/f39/Pz8+vr6wMDAZmZm/f39u7u79PT02tra29vb+vr6+Pj48fHxeHh49vb23d3d/v7+////+Pj4R0dHtLS09vb2/Pz8////9vb2/Pz89/f3/v7+3d3d7Ozs/v7+/v7+AAAA////////AAAA/Pz8AAAAAAAAAAAAAAAA+/v7tLS0AAAAAAAA/v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////gMlA/AAAALR0Uk5TPAxMLBznb1sX7pXISUfmafjg+l/8OiqD1XuwNCjuGEzno3zg9BvGT0E06iTw5Mr3W9hmTsdxUDazMKhL1xGJeW+2XpQrOMU5uZzQG6ty3pN73mLYW7sXR58mXApMLhiv8kRA0/Wx4fPMvpJlQfRtiJib6XHORDgm8P6aNmbcrPpUmn/YDxvk9R78/RzxECknJO5pGSX7FQ0XDh0bIBQTEQkjFhoPHyILBxIIBQwKBgQDAgEAUjpwbwAAAfJJREFUOMuN1PVfFFEQAPATFLERsYMQUMHuRERs7MLu7pYQFVQaBa77brterX+ge7vqce/27pjfdj/fz8zszNvn+D2OcKS9WV6ZG7lK5+dG5fy8BblQ2Vyem1OSAy109jDMouyoegkXj0iLl2ZDd7fyy1hNkCqyoZfOfkkDQK1amxl9agvGVYCQyG5yZULD7bHRQRkRAjVpv8sGvX77auRdLCBpCtF1DNQLF4837R5Iot7hgydHng79/BVkwirAuq4TJPvi/bxzV33znQETnT93wAA85x91RwQRGYkMBWWP5A34g7FLt58n0NlDMd7PeEMRVo0Cy+gEQ6B5WMntDXBPzHJHG5kQq2qyqCBsGYspYlTwDF59YDV++JgvCqAh/hOTEYygeO3+v6874vvbCxUEX3+YHMEpQcF26Mq9MXPqu6xBbJPoccowb96KwrSC+A018RM7ZLot8uUzvbst1rDHGNSRtuCNESoVga006lsf0lK7IsoLGq1bEVeVFITBXhqtXpmOZtGoZpVboMqByTRaUxsSoLmx79+wuWcC8noptIELG40T/P7rj67OD9A4w0RxdFNocwLtOfOoJfHwcef0CQiB/CkU2tcT3lZ3Onn2pxVMlAtnUuhGw/bu1D9y0tQZsyn0zObuKirOeImN66aziT+yRUV0UfcynAAAAABJRU5ErkJggg==) !important;",
                "}",
                "</style>",
                ].join("\n");
            return code + star_image;
        }
    
    
        function displaySource() {
    
            // contents
            var e = $id("source-entries");
            e.value = [].concat([
                "<!DOCTYPE html>",
                "<html>",
                '<head>',
                '<meta charset="utf-8">',
                '<title>' + hspace_title + "(アーカイブ)" + '</title>',
                code_stylesheet(),
                '</head>',
                '<body>',
                '<div id="main-container" style="width: 538px;">',
                '<div class="container">',
                '<div id="entry-container" class="entry-box">',
                '<div id="entries">',
            ],
            hspace_pages.map(function(e) {
                return e.innerHTML;
            }),
            [
                '</div>',
                '</div>',
                '</div>',
                '</div>',
                '</body>',
                "</html>"
            ]).join("\n");
    
            // image list
            var image_list = [];
            hspace_pages.forEach(function(e) {
                Array.from(e.querySelectorAll('a[href^="http://cdn.mogile.archive.st-hatena.com/v1/image/"]')).forEach(function(a) {
                    image_list.push(a.href);
                });
            });
    
            if (image_list.length > 0) {
                // only url
                e = $id("source-imagelist");
                e.value = image_list.join("\n");
    
                // thumbnail page
                e = $id("source-imagepage");
                e.value = [
                    "<!DOCTYPE html>",
                    "<html>",
                    "<head>",
                    "<title>",
                    hspace_title + "(アーカイブ:画像)",
                    "</title>",
                    "<style>",
                    "body {padding: 1ex;}",
                    "img {padding: 1ex;border: 1px solid gray; max-width: 100%;}",
                    "</style>",
                    "</head>",
                    "<body>",
                    image_list.map(function(e) {
                        return '<img src="' + e + '">';
                    }).join("\n"),
                    "</body>",
                    "</html>"].join("\n");
            }
    
            // stylesheet
            var e = $id("source-stylesheet");
            e.value = hspace_stylesheet;
        }
    
        function loadStyleSheet() {
            var xx = new XMLHttpRequest();
            xx.open('GET', style_url, true);
            xx.onload = function(ev) {
                hspace_stylesheet = ev.target.responseText;
            };
            xx.send(null);
        }
        
        function loadEntries() {
            var msg = "Loading page:" + page + " / " + (max_page == -1 ? "?" : max_page) + " ...";
            console.log(msg);
            $id("progress").innerHTML = msg;
            xhr.open('GET', location.pathname + "?p=" + page, true);
            xhr.responseType = 'document';
            xhr.send(null);
        };
    
    //  opt.only_my_entry = confirm([
    //  "Only my POST ?",
    //  "",
    //  "    OK : only my posts (need login)",
    //  "    Cancel : all posts"
    //  ].join("\n"));
    
        createProgressView();
        loadEntries();
        loadStyleSheet();
    
    }());
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2016/01/23 07:57:58 - 2016-01-23
  2. 2016/01/20 14:23:03 - 2016-01-20
  3. 2016/01/19 16:06:24 - 2016-01-19
  4. 2016/01/19 13:58:01 - 2016-01-19