Thursday, September 22, 2011

/b/ackwash revolutions


// ==UserScript==
// @name          /b/ackwash revolutions
// @namespace     http://www.4chan.org
// @description   Add tooltips to 4chan quotes (>>).
// @author        tkirby, aeosynth, VIPPER
// @include       http://*.4chan.org/*
// @include       http://4chanarchive.org/*/dspl_thread.php5?*
// @include       http://archive.easymodo.net/*/*
// @include       http://archive.installgentoo.net/*/*
// @include       http://archive.gentoomen.org/*/*
// @include       http://archive.no-ip.org/*/*
// @include       http://suptg.thisisnotatrueending.com/archive/*
// @exclude       http://dis.4chan.org/*
// @version       0.131
// ==/UserScript==

const TIP_X_OFFSET = 40;        // in pixels
const BACKLINKS = true;         // 4chan imageboards only
const ISOP = " (OP)";           // style for liks to OP
const ISOUT = " (!)";           // style for links to other threads
const ISBACK = "POST";          // style for backlinks (">>POST" for normal links)
const SEPARATOR = " > ";        // backlink separator (" " for a single space)

const d = document;

var div = d.createElement("DIV");
div.className = "reply";
div.id = "bw_tooltip";
div.setAttribute("style", "display: none; position: absolute; padding: 3px;");

var td = d.createElement("TD");
td.id = "bw_tipcell";
td.setAttribute("style", "padding: 0px 5px;");

div.appendChild(d.createElement("TABLE").appendChild(d.createElement("TR").appendChild(td)));
d.body.appendChild(div);

var cache = [];

function bw_wash(post) {
    qts = d.evaluate(".//a[starts-with(text(), '>>')]", post, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    op = window.location.pathname.match(/[0-9]{4,}/) || window.location.search.match(/[0-9]{4,}/) || "";
    backlinkid = "";
    
    for(var i = 0, qt = null; qt = qts.snapshotItem(i++); ) {
        id = qt.hash.split("#")[1];
        
        if(/>>\/(rs|([a-z]+\/($|[^0-9]+)))/i.test(qt.textContent)) { // rs + >>/board/ + easymodo internal
            continue;
        } else if(id == op || id == "p" + op) {
            qt.textContent += ISOP;
        } else if(!d.getElementById(id)) {
            qt.textContent += ISOUT;
            getPost(qt);
        }
        
        if(BACKLINKS) backlink(qt, id);
        
        qt.addEventListener("mouseover", function(e) { bw_show(e, this) }, false);
        qt.addEventListener("mousemove", function(e) { bw_track(e) }, true);
        qt.addEventListener("mouseout", function() { bw_hide() }, false);
        qt.addEventListener("mousedown", function() { bw_hide() }, false);
    }
}

function bw_hide() {
    d.getElementById("bw_tooltip").style.display = "none";
    d.getElementById("bw_tipcell").innerHTML = "";
}

function bw_show(e, me) {
    id = me.hash.split("#")[1];
    tip = d.getElementById("bw_tipcell");
    
    if(id == op) {
        tip.innerHTML = d.body.innerHTML.match(/<span class="filesize">[^]+?<\/blockquote>/)[0].replace(/<input.*?>/i, "");
    } else {
        if(d.getElementById(id)) tip.innerHTML = d.getElementById(id).innerHTML.replace(/<input.*?>/i, "");
        else tip.innerHTML = checkCache(id, me);
    }
    tip.className = "replyhl highlight";
    
    bw_track(e);
}

function bw_track(e) {
    if(!d.getElementById("bw_tipcell").innerHTML) return;
    
    tip = d.getElementById("bw_tooltip");
    tip.style.display = "";
    tip_height = tip.clientHeight / 2;
    
    vp_height = Math.min(d.documentElement.clientHeight, d.body.clientHeight);
    vp_bottom = window.scrollY + vp_height;
    
    if(e.pageY - tip_height < window.scrollY || tip.clientHeight > vp_height) {
        tip_y_offset = window.scrollY;
    } else if(e.pageY + tip_height >= vp_bottom) {
        tip_y_offset = vp_bottom - tip.clientHeight;
    } else {
        tip_y_offset = e.pageY - tip_height;
    }
    
    tip.style.top = tip_y_offset + "px";
    tip.style.left = e.pageX + TIP_X_OFFSET + "px";
}

function backlink(post, id) { // CLUSTERFUCK
    if(!d.getElementById(id) || !/4chan\.org/.test(window.location.hostname) || backlinkid == id) return;
    var pid = d.evaluate("ancestor::td[1]/@id", post, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
    
    link = d.createElement("A");
    link.setAttribute("href", "#" + pid);
    link.setAttribute("onclick", "replyhl(" + pid + ")");
    link.textContent = ISBACK.replace("POST", pid);
    link.addEventListener("mouseover", function(e) { bw_show(e, this) }, false);
    link.addEventListener("mousemove", function(e) { bw_track(e) }, true);
    link.addEventListener("mouseout", function() { bw_hide() }, false);
    link.addEventListener("mousedown", function() { bw_hide() }, false);
    
    pid = d.evaluate(".//span[@id]", d.getElementById(id), null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    pid.parentNode.insertBefore(link, pid.nextSibling);
    pid.parentNode.insertBefore(d.createTextNode(SEPARATOR), pid.nextSibling);
    backlinkid = id;
}

function checkCache(id, url) {
    if(!id) id = url.pathname.match(/[0-9]+/);
    for(var i in cache)
        if(url.href.split("#")[0] == cache[i].url) {
            var responseText = cache[i].post;
            if(responseText == "Loading post") return ("<h3>Loading post #" + id + "...</h3>");
            
            if(!!url.pathname.match(id) && /4chan\.org/.test(url.hostname)) {
                responseText = responseText.match(/<span class="filesize">[^]+?<\/blockquote>/)[0];
            } else {
                var expr = new RegExp("<.*?id\=\"p" + id + "\">.*?([^]+?<\/blockquote>)");
                if(responseText.match(expr)) {
                    responseText = responseText.match(expr);
                    responseText = responseText ? responseText[1] : "<h3>Post not found</h3>";
                } else {
                    expr = new RegExp("<input.*?" + id + ".*?([^]+?<\/blockquote>)");
                    responseText = responseText.match(expr);
                    responseText = responseText ? responseText[0] : "<h3>Post not found</h3>";
                }
            }
            return responseText.replace(/<input.*?>/i, "");
    }
    return "Error"; // Should never happen
}

function getPost(id) {
    link = id.toString().split("#")[0];
    for(var i in cache)
        if(link == cache[i].url)
            return;
    
    cache.push({url: link, post: "Loading post"});
    
    GM_xmlhttpRequest({
        method : "GET",
        url : link,
        headers: {
            "User-agent" : navigator.userAgent,
            "Accept" : "application/xml",
        },
        onload : function(response) {
            for(var i in cache)
                if(id.toString().split("#")[0] == cache[i].url)
                    cache[i].post = response.responseText;
        }
    });
}

d.body.addEventListener("DOMNodeInserted", function(e) {
    if(e.target.nodeName == "TABLE") bw_wash(e.target);
}, true);

var posts = d.evaluate("//blockquote", d, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for(var i = 0, post = null; post = posts.snapshotItem(i++); )
    bw_wash(post);

0 comments:

Post a Comment