Shiichan post filter

By Mr Satori Last update Nov 24, 2009 — Installed 219 times.

There are 6 previous versions of this script.

// ==UserScript==
// @name                     Shiichan post filter (old)
// @namespace           http://dis.4chan.org/enterprise/
// @description     Removes unwanted posts from shiichan.org by regex/programmatic filter.
// @include             http://dis.4chan.org/*
// @exclude             http://dis.4chan.org/list*
// @exclude             http://dis.4chan.org/post
// ==/UserScript==

(function() {
    regexen = [
        (/((DIX!\s*)|(That was VIP quality!\s*)){20,}/),
        /Please try to ignore troll posts!/,
        /http:\/\/userscripts.org\/scripts\/show\/40415/,
        /Please go to http:\/\/tinyurl.com\//,
        /don\'t sage if you're replying to the thread\. sage if you are saying \'fuck this thread, go to \/r\/\' or whatever\. idiot\./
    ];

    filters = [
        function(pd) { return pd.posterTrip() == "!FrOzEn2BUo" || pd.posterTrip() == "!frozEn/Klg" || pd.linksToHidden(); }
        //,
        //function(pd) { return pd.posterTrip() == "!!TthtFzrtPXElUy7" }
    ];

    reallyHide = false;
    modifyTruncatedLinks = true;

    var board;

    function init() {
        var posts = document.getElementsByClassName('post');
        for(var i = 0; i < posts.length; ++i) {
            var child = posts[i].querySelector("blockquote");
            processPost(posts[i], child);
        }

        board = location.pathname.match(/^\/(?:read\/)?(.*?)\//)[1];

        var linkRegex = new RegExp("^http://" + location.host + "/read/" + board + "/(\\d+)/(\\d+)/?$");

        for(var i = 0; i < document.links.length; ++i) {
            var ln = document.links[i];
            var match;

            if(ln.textContent.match(/Post\s+truncated\./)) {
                if(modifyTruncatedLinks)
                    ln.addEventListener('click', untruncate, true);
                continue;
            }

            if(match = ln.href.match(linkRegex)) {
                var thread = match[1];
                var post = match[2];

                //I'm obviously not going to do EXPERT LINKING for links such as ">>4,67,34", but I
                //might do it for links such as ">>493-498" if all of 493 through 498 are visible.
                if(post.match(/\d+$/)) {
                    var target = document.getElementById(thread + "." + post);
                    if(target) {
                        ln.target = thread + "." + post;
                        //JavaScript's scoping is fucking weird, also, GreaseMonkey dicks with the DOM by adding wrappers
                        //that screw things up, so I'm just going to stick with storing a string property on the link.
                        ln.addEventListener('click', function(evt) {
                            evt.preventDefault();
                            location.href = "#" + this.target;
                            //document.getElementById(this.target).scrollIntoView();
                        }, true);
                        //Fuck anyone who doesn't use Pseud0ch.
                        ln.style.color = '#009966';
                    }
                }
            }
        }
    }

    function isPostVisible (threadID, postID) {
        return null != document.getElementById(threadID + "." + postID);
    }

    function getAncestor (elem, predicate) {
        for(var a = elem; a != null; a = a.parentNode)
            if(predicate(a))
                return a;
        return null;
    }

    //When called, 'this' is the 'Post truncated...' link.
    function untruncate (evt) {
        evt.preventDefault();
        var ln = this;

        var marquee = document.createElement("marquee");
        marquee.appendChild(document.createTextNode("Please wait, haxing anii..."));
        marquee.style.width = "20em";
        marquee.setAttribute("scrolldelay", 40);
        marquee.setAttribute("scrollAmount", 7);
        ln.parentNode.appendChild(marquee);

        ln.textContent = "Cancel";
        ln.removeEventListener("click", untruncate, true);

        var bq = getAncestor(ln, function(a) { return a.nodeName.toLowerCase() == "blockquote"; });

        var abort = fetchPost(ln.href, function(success, res) {
            if(success) {
                bq.innerHTML = res;
                // TODO: Call prettify on <code> elements.
            } else {
                ln.textContent = "Failed: " + res[1];
                cancelUntruncate(function(){}, marquee, ln);
                return;
            }
        });

        ln.addEventListener('click', function(e) {
            e.preventDefault();
            cancelUntruncate(abort, marquee, ln);
        }, true);
    }

    function cancelUntruncate (abort, marquee, ln) {
        abort();
        marquee.parentNode.removeChild(marquee);
        ln.removeEventListener('click', cancelUntruncate, true);
        ln.addEventListener('click', untruncate, true);
        ln.textContent = 'Post truncated.';
    }

    function fetchPost (url, f) {
        var req = new XMLHttpRequest();
        req.open('GET', url, true);
        req.onreadystatechange = function() {
            //alert("hma " + req.readyState + " " + req.status);
            if(req.readyState == 4) {
                if (req.status == 200 || req.status == 304) {
                    //Hax my anus does this, so it obviously works. And it's less work than using responseXML.
                    //TODO: Fetch the JSON instead.
                    var comment = req.responseText.split("<blockquote>");
                    comment = comment[1].split("</blockquote>");
                    comment = comment[0].replace(/^\s+|\s+$/g,"");
                    f(true, comment);
                } else {
                    f(false, [req.status, req.statusText]);
                }
            }
        }
        req.send();

        return function() { req.abort(); };
    }

    function expand() {
        post.style.opacity = 1;
        post.style.overflow = "visible";
        post.style.maxHeight = "";

        this.removeEventListener("click", expand, false);
        this.addEventListener("click", collapse, false);
    }

    function collapse() {
        post.style.maxHeight = "1.2em";
        post.style.opacity = 0.5;
        post.style.overflow = "hidden";

        this.removeEventListener("click", collapse, false);
        this.addEventListener("click", expand, false);
    }

    // This cache maps from thread DOM elements to thread IDs.
    // This is bad, but it will be deleted once the script finishes
    // so it's not as bad.
    var threadIDCache = {};

    // This is the slowest part of the script, which is why it is cached.
    function findThreadID(post) {
        var thread = post.parentNode;

        var cached = threadIDCache[thread];
        if(cached)
            return cached;

        var input = thread.parentNode.querySelector('input[name="id"]');
        threadID = parseInt(input.value);

        return threadIDCache[thread] = threadID;
    }

    function processPost (post, blockquote) {
        var h3 = post.querySelector('h3');
        var id, threadID;

        threadID = findThreadID(post);

        //Get the post ID from the little clickable number link to the left.
        if(h3)
            id = parseInt(h3.querySelector('span').textContent);

        if(threadID && id)
            post.id = threadID + "." + id;

        var text = blockquote.textContent;

        function makeHidden() {
            if(reallyHide) {
                post.style.display = "none";
                return;
            }

            var span = document.createElement("SPAN");
            span.style.fontStyle = "italic";
            span.appendChild(document.createTextNode("(Hidden: click to expand/collapse)"));
            h3 && h3.lastChild && h3.lastChild.appendChild(span);

            collapse.apply(span);
        }

        for(var i = 0; i < regexen.length; ++i) {
            if (regexen[i].test(text)) {
                makeHidden();
                return;
            }
        }

        if(filters.length) {
            function attrib (attr) {
                var hasCached = false, cached = null;
                return function () {
                    if(hasCached)
                        return cached;
                    var elem = this.domElement.querySelector('.'+attr);
                    hasCached = true;
                    return cached = elem ? elem.textContent : null;
                };
                /*var elems = post.getElementsByClassName(attr);
                if(elems.length > 0)
                    return elems[0].textContent;
                return null;*/
            }

            var board = location.pathname.match(/^\/(?:read\/)?(.*?)\//)[1];
            var linkRegex = new RegExp("^http://" + location.host + "/read/" + board + "/(\\d+)/(\\d+)/?$");

            var fullInfo = {
                postID : id,
                threadID : threadID,
                posterName : attrib('postername'),
                posterTrip : attrib('postertrip'),
                posterDate : attrib('posterdate'),
                posterID : attrib('id'),
                fullPostInfoLine : attrib('postinfo'), //Includes 'Name: ' etc.
                domElement : post,
                postContent : text,
                board : board,
                linksToHidden : function() {
                    var links = this.domElement.getElementsByTagName("A");
                    for(var i = 0; i < links.length; ++i) {
                        var matches = links[i].href.match(linkRegex);
                        if(matches) {
                            var elem = document.getElementById(matches[1] + "." + matches[2]);
                            if(elem && elem.style && (elem.style.overflow == "hidden" || elem.style.display == "none"))
                                return true;
                        }
                    }
                    return false;
                },
                getAttr : function(attr) {
                    return attrib(attr);
                },
                setAttr : function(attr, value) {
                    var elem = this.domElement.getElementsByClassName(attr);
                    if(elems.length > 0)
                        elems[0].textContent = value;
                }
            };

            for(var i = 0; i < filters.length; ++i) {
                if(filters[i](fullInfo)) {
                    makeHidden();
                    return;
                }
            }
        }
    }

    window.addEventListener('load', function(evt) {
        init();
        threadIDCache = null;
    }, false);
})();