Reddit Chemo

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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});
})();