Reddit Chemo

Filter, block, and remove unwanted subreddit posts of your choosing and remove ads on the Reddit feed.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name        Reddit Chemo
// @namespace   https://lawrenzo.com/p/reddit-chemo
// @version     2.2.0
// @description Filter, block, and remove unwanted subreddit posts of your choosing and remove ads on the Reddit feed.
// @author      Lawrence Sim
// @license     WTFPL (http://www.wtfpl.net)
// @grant       unsafeWindow
// @grant       GM_getValue
// @grant       GM.getValue
// @grant       GM_setValue
// @grant       GM.setValue
// @match       *://*.reddit.com/*
// @noframes
// ==/UserScript==
(function() {
    //-------------------------------------------------------------------------------------
    // Prepare styles
    //-------------------------------------------------------------------------------------
    let styles = {
        ".rchemo-post": {
            background:        "rgb(255 240 240)"
        },
        ".rchemo-dark .rchemo-post": {
            background:        "rgb(80 70 70)"
        },
        ".rchemo-post.rchemo-card": {
            height:            "2em"
        },
        ".rchemo-post div[data-click-id='background']": {
            background:        "none !important",
            color:             "rgb(163 149 149)",
            'font-size':       "0.7em",
            height:            "1.6em",
            'line-height':     "1.7em"
        },
        ".rchemo-post.rchemo-classic div[data-click-id='background']": {
            padding:           "0.1em 0 0.15em 0.7em"
        },
        ".rchemo-post.rchemo-compact div[data-click-id='background']": {
            padding:           "0",
            "font-size":       "0.6em"
        },
        ".rchemo-post.rchemo-card div[data-click-id='background']": {
            'border-left':     "none",
            padding:           "0.2em 0.4em",
            "font-size":       "0.95em"
        },
        ".rchemo-post button[data-click-id='downvote'] .icon": {
            top:               "2px",
            left:              "2px",
            'line-height':     "14px",
            'font-size':       "14px"
        },
        ".rchemo-post.rchemo-classic .voteButton, .rchemo-post.rchemo-classic .voteButton span": {
            width:             "46px"
        },
        ".rchemo-post.rchemo-card button[data-click-id='downvote'] .icon": {
            top:               "8px",
            left:              "2px",
            'line-height':     "20px",
            'font-size':       "20px"
        },
        ".rchemo-post.rchemo-card .voteButton, .rchemo-post.rchemo-card .voteButton span": {
            width:             "46px",
            height:            "36px"
        },
        ".rchemo-unban": {
            margin:            "0",
            'margin-left':     "0.6em",
            padding:           "0.1em 0.4em",
            background:        "rgb(135 158 200)",
            'border-radius':   "2px",
            color:             "#fff",
            'line-height':     "normal"
        },
        ".rchemo-dark .rchemo-unban": {
            background:        "rgb(145 125 125)",
            color:             "#000"
        },
        ".rchemo-post.rchemo-compact .rchemo-unban": {
            padding:           "0 0.4em",
            'line-height':     "1.2em"
        },
        ".rchemo-post.rchemo-card .rchemo-unban": {
            'font-size':      "0.85em"
        },
        ".rchemo-unban:hover": {
            background:        "rgb(185 200 220)"
        },
        ".rchemo-dark .rchemo-unban:hover": {
            background:        "rgb(205 180 180)"
        },
        ".rchemo-join, .rchemo-ban": {
            'border-radius':   "0.9em"
        },
        ".rchemo-classic .rchemo-join, .rchemo-compact .rchemo-join": {
            'min-height':      "auto",
            padding:           "0.3em 0.6em",
            'line-height':     "0.8em",
            'font-size':       "0.7em"
        },
        ".rchemo-ban": {
            margin:            "0 0.2em 0 0.4em",
            background:        "rgb(120 45 45)",
            padding:           "0.34em 1.2em",
            color:             "#fff",
            'line-height':     "1.2em",
            'font-size':       "1.06em"
        },
        ".rchemo-ban:hover": {
            background:      "rgb(180 85 85)"
        },
        ".rchemo-classic .rchemo-ban, .rchemo-compact .rchemo-ban": {
            padding:           "0.2em 0.6em",
            'line-height':     "0.8em",
            'font-size':       "0.75em"
        },
        ".rchemo-compact .rchemo-ban": {
            'font-size':       "0.85em"
        },
        ".rchemo-counter": {
            position:          "fixed",
            top:               "60px",
            right:             "25px",
            width:             "120px",
            "z-index":         "70",
            background:        "rgb(255 240 240)",
            border:            "1px solid rgb(123, 106, 109)",
            "border-radius":   "0.2em",
            padding:           "0.2em 0.4em",
            "text-align":      "center",
            "font-size":       "0.75em",
            color:             "#333"
        },
        ".rchemo-dark .rchemo-counter": {
            background:        "rgb(80 70 70)",
            border:            "1px solid rgb(123, 106, 109)",
            color:             "#ddd"
        },
        ".rchemo-counter-content": {
            "margin-top":      "0.4em",
            display:           "none"
        },
        ".rchemo-counter:hover > .rchemo-counter-content": {
            display:           "block"
        },
        ".rchemo-counter p": {
            padding:           "0",
            margin:            "0"
        },
        ".rchemo-counter button": {
            "margin-bottom":   "0.2em"
        },
        ".rchemo-btn-showhide, .rchemo-btn-editlist": {
            "margin-top":      "0.2em",
            "text-decoration": "underline",
            color:             "rgb(77, 113, 68)"
        },
        ".rchemo-dark .rchemo-btn-showhide, .rchemo-dark .rchemo-btn-editlist": {
            color:             "rgb(182, 198, 178)"
        },
        ".rchemo-btn-showhide:hover, .rchemo-btn-editlist:hover": {
            color:             "rgb(141, 187, 128) !important"
        },
        ".rchemo-btn-darkmode": {
            "margin-top":      "0.2em",
            background:        "rgb(135, 158, 200)",
            color:             "#eee",
            padding:           "0.2em 0.5em",
            "border-radius":   "0.6em",
            "border":          "1px solid #888"
        },
        ".rchemo-btn-darkmode:hover": {
            "border-color":    "#444"
        },
        ".rchemo-dark .rchemo-btn-darkmode": {
            background:        "rgb(0 0 0)",
            color:             "#aaa"
        },
        ".rchemo-dark .rchemo-btn-darkmode:hover": {
            "border-color":    "#fff"
        },
        ".rchemo-btn-support": {
            "text-decoration": "underline",
            "font-size":       "0.85em",
            color:             "rgb(90, 108, 140)"
        },
        ".rchemo-btn-support:hover": {
            color:             "rgb(135, 158, 200)"
        },
        ".rchemo-dark .rchemo-btn-support": {
            color:             "rgb(135, 158, 200)"
        },
        ".rchemo-dark .rchemo-btn-support:hover": {
            color:             "rgb(208, 225, 255)"
        },
        ".rchemo-edit-container": {
            position:          "fixed",
            top:               "50%",
            left:              "50%",
            transform:         "translate(-50%,-50%)",
            "z-index":         "90",
            width:             "240px",
            background:        "rgb(255 240 240)",
            'border-radius':   "0.2em",
            border:            "1px solid rgb(123, 106, 109)",
            padding:           "0.2em 0",
            "font-size":       "0.9em",
            color:             "#333",
            'user-select':     "none",
            cursor:            "default"
        },
        ".rchemo-dark .rchemo-edit-container": {
            background:        "rgb(80 70 70)",
            border:            "1px solid rgb(123, 106, 109)",
            color:             "#ddd"
        },
        ".rchemo-edit-container p": {
            padding:           "0 0.2em"
        },
        ".rchemo-edit-container ul": {
            height:            "240px",
            width:             "100%",
            'overflow-y':      "scroll",
            'overflow-x':      "hidden",
            background:        "#fff",
            'border-top':      "1px solid #333",
            'border-bottom':   "1px solid #333",
            "box-sizing":      "border-box",
            "list-style":      "none"
        },
        ".rchemo-dark  .rchemo-edit-container ul": {
            background:        "#222",
            'font-size':       "0.95em",
            padding:           "0 0.2em",
            'user-select':     "none",
            'border-color':    "#bbb"
        },
        ".rchemo-edit-container li": {
            position:          "relative",
            cursor:            "pointer",
            padding:           "0.2em",
            'padding-left':    "1.2em",
            cursor:            "pointer"
        },
        ".rchemo-dark .rchemo-edit-container li:hover": {
            background:        "rgb(36, 43, 49)"
        },
        ".rchemo-edit-container li:hover": {
            background:        "rgb(238, 246, 253)"
        },
        ".rchemo-edit-container li.checked": {
            background:        "rgb(200, 223, 244)"
        },
        ".rchemo-dark .rchemo-edit-container li.checked": {
            background:        "rgb(79, 91, 102)"
        },
        ".rchemo-edit-container li.checked::before": {
            position:          "absolute",
            left:              "0.2em",
            content:           "'\\2713'",
            color:             "rgb(43, 151, 20)"
        },
        ".rchemo-dark .rchemo-edit-container li.checked::before": {
            color:             "rgb(129, 238, 106)"
        },
        ".rchemo-edit-buttons": {
            'text-align':      "right",
            margin:            "0.4em 0 0.2em 0",
            'padding-right':   "0.5em"
        },
        ".rchemo-edit-cancel, .rchemo-edit-submit": {
            color:             "#eee",
            padding:           "0.2em 0.5em",
            "border-radius":   "0.1em",
            'font-size':       "0.9em"
        },
        ".rchemo-edit-cancel": {
            background:        "rgb(135, 135, 135)"
        },
        ".rchemo-edit-cancel:hover": {
            background:        "rgb(90, 90, 90)"
        },
        ".rchemo-edit-submit": {
            background:        "rgb(135, 158, 200)",
            "margin-left":     "0.6em",
        },
        ".rchemo-edit-submit:hover": {
            background:        "rgb(67, 104, 170)"
        }
    };
    let styletxt = "";
    for(let selector in styles) {
        styletxt += `${selector} {`;
        for(let skey in styles[selector]) {
            styletxt += `${skey}: ${styles[selector][skey]};`;
        }
        styletxt += "}";
    }
    let styleElem = document.createElement('style');
    styleElem.className = 'rchemo-styles';
    styleElem.innerText = styletxt;
    document.body.appendChild(styleElem);
    var cssRules = Array.from(document.styleSheets[document.styleSheets.length-1].cssRules);
    //-------------------------------------------------------------------------------------
    // handlers for banned list
    //-------------------------------------------------------------------------------------
    GM_getValue = GM_getValue || GM.getValue;
    GM_setValue = GM_setValue || GM.setValue;
    var banSubreddits = refreshBanned();
    function refreshBanned() {
        banSubreddits = (
            (GM_getValue("banned") && GM_getValue("banned").split("|"))
            || window.bannedSubreddits
            || (unsafeWindow && unsafeWindow.bannedSubreddits)
            || []
        );
        banSubreddits = banSubreddits.map(n => n.trim().toLowerCase())
                                     .map(n => n.startsWith("r/") ? n : `r/${n}`);
        banSubreddits.sort();
        return banSubreddits;
    }
    function addBanned(subreddit) {
        banSubreddits.push(subreddit.trim().toLowerCase().startsWith("r/") ? subreddit : `r/${subreddit}`);
        banSubreddits.sort();
        GM_setValue("banned", banSubreddits.join("|"));
    }
    function removeBanned(subreddit) {
        subreddit = subreddit.startsWith("r/") ? subreddit : `r/${subreddit}`;
        let index = banSubreddits.indexOf(subreddit);
        if(~index) {
            banSubreddits.splice(index, 1);
            GM_setValue("banned", banSubreddits.join("|"));
        }
    }
    //-------------------------------------------------------------------------------------
    // control element and options
    //-------------------------------------------------------------------------------------
    var controlElem = document.createElement('div');
    controlElem.className = 'rchemo-counter';
    controlElem.innerHTML = (
        "<p style='font-weight:bold'>Reddit Chemo</p>" +
        "<div class='rchemo-counter-content'>" +
          "<p>Posts blocked: <span class='rchemo-count'>0</span></p>" +
          "<p>Ads blocked: <span class='rchemo-adcount'>0</span></p>" +
          "<button class='rchemo-btn-showhide'></button>" +
          "<button class='rchemo-btn-editlist'>Edit banned list</button>" +
          "<button class='rchemo-btn-darkmode'>light mode</button>" +
          "<a href=\"https://ko-fi.com/F1F25YGLA\" rel=\"nofollow\" target=\"blank\"><button class='rchemo-btn-support'>Buy me a coffee</button></a>" +
        "</div>"
    );
    document.body.appendChild(controlElem);
    function showControl() { controlElem.style.display = ""; }
    function hideControl() { controlElem.style.display = "none"; }

    var countElem = controlElem.querySelector(".rchemo-count"),
        adCountElem = controlElem.querySelector(".rchemo-adcount"),
        adblockCount = 0,
        blockCount = 0;
    function resetCount() { countElem.innerHTML = blockCount = adblockCount = 0; }
    function incrementCount() { countElem.innerHTML = ++blockCount; }
    function incrementAdCount() { adCountElem.innerHTML = ++adblockCount; }

    var showHideBtn = controlElem.querySelector(".rchemo-btn-showhide"),
        postCssRule = cssRules.filter(r => r.selectorText == ".rchemo-post")[0],
        showBanned  = GM_getValue("showbanned");
    if(showBanned === null || typeof showBanned === "undefined") showBanned = true;
    function setShowBanned(visible) {
        showBanned = !!visible;
        GM_setValue("showbanned", showBanned);
        showHideBtn.innerHTML = (showBanned ? "Hide" : "Show") + " blocked posts";
        postCssRule.style.display = showBanned ? "" : "none";
    };
    setShowBanned(showBanned);
    showHideBtn.addEventListener('click', () => setShowBanned(!showBanned));

    var darkBtn = controlElem.querySelector(".rchemo-btn-darkmode"),
        darkmode = GM_getValue("darkmode");
    function setDarkMode(darkOn) {
        darkmode = !!darkOn;
        GM_setValue("darkmode", darkOn);
        if(darkmode) {
            document.body.classList.add("rchemo-dark");
            darkBtn.innerHTML = "dark mode";
        } else {
            document.body.classList.remove("rchemo-dark");
            darkBtn.innerHTML = "light mode";
        }
    }
    setDarkMode(darkmode);
    darkBtn.addEventListener('click', () => setDarkMode(!darkmode));
    //-------------------------------------------------------------------------------------
    // editing the list
    //-------------------------------------------------------------------------------------
    var editBtn = controlElem.querySelector(".rchemo-btn-editlist");
    editBtn.addEventListener('click', evt => {
        evt.stopPropagation();
        openEditor();
    });
    var editWindow = null;
    function closeEditor(unbanList) {
        if(editWindow) {
            editWindow.remove();
            editWindow = null;
        }
        if(!unbanList || !unbanList.length) return;
        unbanList.forEach(subreddit => removeBanned(subreddit));
    }
    function openEditor() {
        if(editWindow) closeEditor();
        editWindow = document.createElement('div');
        editWindow.className = 'rchemo-edit-container';
        editWindow.innerHTML = (
            "<p style='font-weight:bold'>Reddit Chemo (Ban List)</p>" +
            "<p style='margin:0.4em 0;font-size:0.9em;'>Select/highlight subreddits to remove from the ban list below. (A refresh will be required to show previously hidden posts.)</p>" +
            "<ul class='rchemo-edit-list'></ul>" +
            "<div class='rchemo-edit-buttons'>" +
              "<button class='rchemo-edit-cancel'>Cancel</button>" +
              "<button class='rchemo-edit-submit'>Apply</button>" +
            "</div>"
        );
        var listElem = editWindow.querySelector(".rchemo-edit-list"),
            unban = [];
        banSubreddits.forEach(subreddit => {
            let listSubreddit = document.createElement("li");
            listSubreddit.innerHTML = subreddit;
            listSubreddit.addEventListener('click', () => {
                let ubindex = unban.indexOf(subreddit);
                if(~ubindex) {
                    unban.splice(ubindex, 1);
                    listSubreddit.classList.remove("checked");
                } else {
                    unban.push(subreddit);
                    listSubreddit.classList.add("checked");
                }
            });
            listElem.append(listSubreddit);
        });
        editWindow.querySelector(".rchemo-edit-cancel").addEventListener('click', closeEditor);
        editWindow.querySelector(".rchemo-edit-submit").addEventListener('click', () => closeEditor(unban));
        document.body.appendChild(editWindow);
    }
    document.body.addEventListener('click', evt => {
        if(!editWindow) return;
        if(!editWindow.contains(evt.target)) closeEditor();
    });
    //-------------------------------------------------------------------------------------
    // block post function
    //-------------------------------------------------------------------------------------
    function blockPost(post, sub, mode) {
        post.classList.add("rchemo-post");
        if(mode == 'compact') post = post.children[0];
        let child, voteElem, subelm, icon;
        Array.from(post.children).forEach(child => {
            if(child.getAttribute("data-click-id") === "background") {
                child.innerHTML = `Post from ${sub} removed`;
                let rmvBtn = document.createElement("button");
                rmvBtn.innerHTML = `Remove ban`;
                rmvBtn.classList.add("rchemo-unban");
                rmvBtn.addEventListener('click', function() {
                    this.parentNode.innerHTML = `Post from ${sub} removed from banned list (refresh for reload)`;
                    this.remove();
                    removeBanned(sub);
                });
                child.append(rmvBtn);
                return;
            }
            let downvote = child.querySelectorAll("#vote-arrows-"+post.id + " button[data-click-id='downvote']");
            if(downvote && downvote.length) {
                child.style.top = "-0.7em";
                for(let j = 0; j < downvote.length; ++j) {
                    let voteElem = downvote[j].parentNode;
                    voteElem.style.margin = 0;
                    voteElem.style.padding = 0;
                    voteElem.parentNode.style.border = "none";
                    voteElem.innerHTML = "";
                    voteElem.append(downvote[j]);
                }
                return;
            }
            child.remove()
        });
        incrementCount();
    }
    //-------------------------------------------------------------------------------------
    // watchers as React will sometimes restore/readd posts
    //-------------------------------------------------------------------------------------
    var emptyObserver = new MutationObserver(mutated => {
        mutated.forEach(mutant => {
            if(mutant.target.children.length) {
                mutant.target.innerHTML = "";
                mutant.target.style.border = "none";
                mutant.target.style.fill = "none";
            }
        });
    });
    var blockObserver = new MutationObserver(mutated => {
        mutated.forEach(mutant => {
            if(mutant.target.children.length) {
                let post = mutant.target.closest("div[data-testid='post-container']");
                blockPost(post, post.getAttribute("chemo"));
            }
        });
    });
    function refreshObservers() {
        emptyObserver.disconnect();
        blockObserver.disconnect();
    };
    //-------------------------------------------------------------------------------------
    // resolvers for when post data is not yet loaded
    //-------------------------------------------------------------------------------------
    function watchPost(post) {
        (new MutationObserver((mutated, observer) => {
            if(checkAd(post)) return observer.disconnect();
            let subreddit = getSubredditNode(post);
            if(checkBanned(post, subreddit)) return observer.disconnect();
        })).observe(post, {childList:true, subtree:true});
    }
    function watchSubreddit(post, subreddit) {
        (new MutationObserver((mutated, observer) => {
            if(checkBanned(post, subreddit)) observer.disconnect();
        })).observe(subreddit, {childList:true, attributes:true});
    }
    //-------------------------------------------------------------------------------------
    // process/check post functions
    //-------------------------------------------------------------------------------------
    function checkAd(post) {
        if(
            Array.from(post.querySelectorAll("span"))
                 .find(span => span.innerText && span.innerText.toLowerCase() === "promoted")
        ) {
            post.innerHTML = "";
            post.style.border = "none";
            post.style.fill = "none";
            console.log("Ad removed.");
            emptyObserver.observe(post, {childList:true});
            incrementAdCount();
            return 1;
        }
        return 0;
    }
    function checkBanned(post, subreddit) {
        let mode = 'classic';
        if(post.children.length === 1) {
            mode = 'compact';
            post.classList.add("rchemo-compact");
        } else if(post.querySelectorAll("a[data-click-id='subreddit']").length > 1) {
            mode = 'card';
            post.classList.add("rchemo-card");
        } else {
            post.classList.add("rchemo-classic");
        }
        if(subreddit && subreddit.innerText) {
            let subname = subreddit.innerText.toLowerCase();
            if(~banSubreddits.indexOf(subname)) {
                post.setAttribute("chemo", subreddit.innerText);
                blockPost(post, subname, mode);
                console.log(`Banned subreddit (${subname}) post removed.`);
                blockObserver.observe(
                    post.querySelector("div[data-click-id='background']"),
                    {childList:true}
                );
                return 1;
            }
            let addBtn = document.createElement("button");
            addBtn.innerHTML = `Ban`;
            addBtn.classList.add("rchemo-ban");
            addBtn.addEventListener('click', function() {
                this.remove();
                addBanned(subname);
                blockPost(post, subname, mode);
            });
            let subscribeBtn = post.querySelector("#subscribe-button-"+post.id);
            if(subscribeBtn) {
                subscribeBtn.after(addBtn);
                subscribeBtn.classList.add("rchemo-join");
            } else if(mode == 'compact') {
                subreddit.after(addBtn);
            }
            return -1;
        }
        return 0;
    }
    function getSubredditNode(post) {
        let subreddit = post.querySelectorAll("a[data-click-id='subreddit']");
        if(!subreddit || !subreddit.length) return null;
        for(let i = 0; i < subreddit.length; ++i) {
            // card layout has two subreddit click elements, one with icon/image
            if(!subreddit[i].children.length) return subreddit[i];
        }
    }
    function processNodes(nodes, refresh) {
        if(!nodes || !nodes.length) {
            if(refresh) hideControl();
            return;
        }
        let found = 0;
        nodes.forEach(node => {
            if(!node || !node.querySelectorAll) return;
            node.querySelectorAll("div[data-testid='post-container']").forEach(post => {
                ++found;
                if(post.getAttribute("chemo")) return;
                post.setAttribute("chemo", 1);
                if(checkAd(post)) return;
                let subreddit = getSubredditNode(post);
                if(!subreddit) return watchPost(post);
                if(!checkBanned(post, subreddit)) watchSubreddit(post, subreddit);
            });
        });
        if(refresh) {
            found > 1 ? showControl() : hideControl();
        }
    }
    //-------------------------------------------------------------------------------------
    // init
    //-------------------------------------------------------------------------------------
    processNodes([document.querySelector(".ListingLayout-outerContainer")], true);
    //-------------------------------------------------------------------------------------
    // if Reddit Watcher available, use it for update/change hooks
    //-------------------------------------------------------------------------------------
    let redditWatcher = window.redditWatcher || (unsafeWindow && unsafeWindow.redditWatcher);
    if(redditWatcher) {
        redditWatcher.feed.onChange(feed => {
            resetCount();
            refreshObservers();
            processNodes([feed], true);
        });
        redditWatcher.feed.onUpdate((feed, mutated) => {
            mutated && mutated.forEach(mutant => processNodes(mutant.addedNodes));
        });
        return;
    }
    //-------------------------------------------------------------------------------------
    // otherwise manually create watcher
    //-------------------------------------------------------------------------------------
    function getFeedWrapper() {
        let listingLayout = document.querySelector(".ListingLayout-outerContainer"),
            firstPost     = listingLayout && listingLayout.querySelector("div[data-testid='post-container']"),
            feedWrapper   = firstPost && firstPost.parentNode;
        while(feedWrapper && !feedWrapper.nextSibling) {
            if(feedWrapper == listingLayout) return null;
            feedWrapper = feedWrapper.parentNode || null;
        }
        return feedWrapper && feedWrapper.parentNode;
    }
    var feedWatcher = new MutationObserver(mutated => mutated.forEach(mutant => processNodes(mutant.addedNodes))),
        lastFeedWrapper = null;
    (new MutationObserver(() => {
        let feedWrapper = getFeedWrapper();
        if(feedWrapper !== lastFeedWrapper) {
            resetCount();
            refreshObservers();
            feedWatcher.disconnect();
            if(feedWrapper) {
                processNodes([feedWrapper], true);
                feedWatcher.observe(feedWrapper, {childList:true});
                lastFeedWrapper = feedWrapper;
            }
        }
    })).observe(document.body, {childList:true, subtree:true});
})();