Nextdoor.com - Hide all annoying content in the Nextdoor.com News Feed

This is a content filter that will hide all annoying content in the Nextdoor News Feed and add an instant "Hide" button to all posts that are shown. Customize with enable-flags and title phrases. It seems like about 99% of the content on Nextdoor is garbage but that 1% of useful posts are worth digging through all the junk. This script helps improve the ratio of garbage.

目前為 2020-01-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Nextdoor.com - Hide all annoying content in the Nextdoor.com News Feed
// @namespace    http://tampermonkey.net/
// @version      0.2.2
// @description  This is a content filter that will hide all annoying content in the Nextdoor News Feed and add an instant "Hide" button to all posts that are shown. Customize with enable-flags and title phrases. It seems like about 99% of the content on Nextdoor is garbage but that 1% of useful posts are worth digging through all the junk. This script helps improve the ratio of garbage.
// @author       Lepricon
// @match        https://nextdoor.com/*
// @match        https://nextdoor.com/news_feed/
// @match        https://nextdoor.com/news_feed/?ordering=*
// @grant        none
// @run-at       document-start
// jshint esversion: 6
// ==/UserScript==

(function() {
    'use strict';

    // --- Configurations start
    const enableHidePaidAds = true;
    const enableHideSponsoredAds = true;
    const enableHideNewNeighborAnnouncements = true;
    const enableHideNonFreeClassifiedAds = true;
    const enableAddHideLinkToPosts = true;

    // The following setting will hide any posts that have a matching tag set. Must be all lower-case.
    const hideTaggedInterestNames = ["dogs", "cats", "yoga", "animal adoption"];

    // The following setting will hide any posts that have titles that contain any of these key phrases. Must be all lower-case.
    const hideKeyPhrases = ["lost dog", "missing dog", "found dog", "dog found", "lost cat", "missing cat", "found cat", "cat found", "dog walker", "coyote", "handyman", "loose"];

    // The following setting will hide any posts with any of these author names. Name match exactly, including case. Useful for those sneaky "pay to read" news sites like San Diego Union-Tribune that otherwise look like legitimate posts.
    const hideAuthorsNames = ["The San Diego Union-Tribune"];

    // Set the following setting true to send a request to the nextdoor server to hide posts permanently. Only applies to key phrases, authors, and individual "new neighbor announcements".
    const enablePermanentHide = true;
    // --- Configurations end

    const shouldHidePostItem = (postItem) => {
        if (enableHideNonFreeClassifiedAds) {
            // Single classified item
            if (postItem.classified && (postItem.classified.price || "").toLowerCase() !== "free") {
                return true;
            }

            // Multiple classified items
            if (postItem.classified_items && postItem.classified_items.filter((classifiedItem) => (classifiedItem.price || "").toLowerCase() === "free").length === 0) { // count of free items
                return true;
            }
        }

        return false;
    }

    const shouldHideFeedItem = (feedItem) => {
        if (enableHideNewNeighborAnnouncements) {
            // Multiple people
            if (feedItem.nmas && feedItem.nmas.find((nma) => nma.presentation_features && nma.presentation_features.welcome)) {
                return true;
            }

            // Single person
            if (feedItem.presentation_features && feedItem.presentation_features.welcome) {
                return true;
            }
        }

        if (hideAuthorsNames && hideAuthorsNames.length > 0) {
            if (feedItem.author && feedItem.author.name) {
                if (hideAuthorsNames.includes(feedItem.author.name)) {
                    return true;
                }
            }
        }

        if (hideTaggedInterestNames && hideTaggedInterestNames.length > 0) {
            if (feedItem.tagged_interest && feedItem.tagged_interest.name) {
                if (hideTaggedInterestNames.includes(feedItem.tagged_interest.name.toLowerCase())) {
                    return true;
                }
            }
        }

        if (hideKeyPhrases && hideKeyPhrases.length > 0) {
            if (feedItem.subject) {
                const subject = feedItem.subject.toLowerCase();
                if (hideKeyPhrases.find((hideKeyPhrase) => subject.includes(hideKeyPhrase))) {
                    return true;
                }
            }
        }

        return false;
    }

    // create XMLHttpRequest proxy object
    var oldXMLHttpRequest = XMLHttpRequest;

    // define constructor for an proxy object to intercept all AJAX traffic for this site so we can filter it
    XMLHttpRequest = function() {
        var actual = new oldXMLHttpRequest();
        var self = this;

        this.onreadystatechange = null;

        // this is the actual handler on the real XMLHttpRequest object
        actual.onreadystatechange = function() {
            if (this.readyState == 4) {
                // Intercept the responses
                // actual.responseText is the ajax result

                // Only apply to the actual news_feed. Email article links contain the base news_feed URL so we do not want to block those.
                const isTargetPage = window.location.href === "https://nextdoor.com/news_feed/"
                        || window.location.href.includes("nextdoor.com/news_feed/?ordering=");

                if (!actual.responseText) {
                    self.responseText = actual.responseText;
                }
                else {
                    try {
                        let changes = 0;
                        let responseJson;

                        if (isTargetPage) {
                            responseJson = JSON.parse(actual.responseText);

                            if (responseJson.posts && responseJson.posts.length > 0) {
                                for (let i = responseJson.posts.length - 1; i >= 0; i--) {
                                    const postItem = responseJson.posts[i];
                                    if (postItem && shouldHidePostItem(postItem)) {
                                        responseJson.posts.splice(i, 1);
                                        responseJson.feed_items.splice(i, 1);
                                        changes++;
                                        if (enablePermanentHide) {
                                            if (postItem.classified && postItem.classified.id) {
                                                const postId = postItem.classified.id;
                                                setTimeout(() => {
                                                    try {
                                                        window.jQuery.post(`/api/classifieds/${postId}/hide`); // tell the server to hide this post
                                                    }
                                                    catch (error) {
                                                        console.log(`Post to /api/classifieds/${postId}/hide failed.`)
                                                    }
                                                }, 250);
                                            }
                                        }
                                    }
                                }
                            }

                            if (responseJson.feed_items && responseJson.feed_items.length > 0) {
                                for (let i = responseJson.feed_items.length - 1; i >= 0; i--) {
                                    const feedItem = responseJson.feed_items[i];
                                    if (feedItem && shouldHideFeedItem(feedItem)) {
                                        responseJson.posts.splice(i, 1);
                                        responseJson.feed_items.splice(i, 1);
                                        changes++;
                                        if (enablePermanentHide) {
                                            if (feedItem.nmas) {
                                                for (let j = 0; j < feedItem.nmas.length; j++) {
                                                    const nma = feedItem.nmas[j];
                                                    if (!nma) {
                                                        continue;
                                                    }

                                                    const postId = nma.id;
                                                    if (!postId) {
                                                        continue;
                                                    }
                                                    setTimeout(() => {
                                                        try {
                                                            window.jQuery.post("/ajax/confirm_mute_post/", `post_id=${postId}`); // tell the server to hide this post
                                                        }
                                                        catch (error) {
                                                            console.log(`Post to /ajax/confirm_mute_post/ for post_id=${postId} failed.`)
                                                        }
                                                    }, 250);
                                                }
                                            }
                                            else if (feedItem.id) {
                                                const postId = feedItem.paged_comments.post_id;
                                                setTimeout(() => {
                                                    try {
                                                        window.jQuery.post("/ajax/confirm_mute_post/", `post_id=${postId}`); // tell the server to hide this post
                                                    }
                                                    catch (error) {
                                                        console.log(`Post to /ajax/confirm_mute_post/ for post_id=${postId} failed.`)
                                                    }
                                                }, 250);
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if (changes > 0) {
                            self.responseText = JSON.stringify(responseJson);
                        }
                        else {
                            self.responseText = actual.responseText;
                        }

                        setTimeout(() => {
                            if (enableAddHideLinkToPosts) {
                                addHideLinkToPosts();
                            }
                            // Do some imperceptible micro scrolling to load more data. Nextdoor looks for scrolling to determine if it should try to load more data. Will only load if the "Load More" area is visible at the bottom of the posts.
                            window.scrollBy(0, 1);
                            window.scrollBy(0, -1);
                        }, 100);
                    }
                    catch (error) {
                        self.responseText = actual.responseText; // Let the actual response pass through
                    }
                }
            }
            if (self.onreadystatechange) {
                return self.onreadystatechange();
            }
        };

        // add all proxy getters
        ["status", "statusText", "responseType", "response",
         "readyState", "responseXML", "upload"].forEach(function(item) {
            Object.defineProperty(self, item, {
                get: function() {return actual[item];}
            });
        });

        // add all proxy getters/setters
        ["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {
            Object.defineProperty(self, item, {
                get: function() {return actual[item];},
                set: function(val) {actual[item] = val;}
            });
        });

        // add all pure proxy pass-through methods
        ["addEventListener", "send", "open", "abort", "getAllResponseHeaders",
         "getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {
            Object.defineProperty(self, item, {
                value: function() {return actual[item].apply(actual, arguments);}
            });
        });
    }

    let oldXHROpen = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
        // do something with the method, url and etc.
        this.addEventListener('load', function() {
            // do something with the response text
            this.responseText = "";
            console.log('load: ' + this.responseText);
        });

        return oldXHROpen.apply(this, arguments);
    }

    const addHideLinkToPosts = () => {
        const $ = window.jQuery; // Use jQuery object provided by the site

        const allPosts = $("article.post-container, div.classifieds-single-item-content");
        allPosts.each((index, el) => {
            const post = $(el);
            const existingHideLink = post.find("a.addon-hide-link");
            if (existingHideLink.length > 0) {
                return; // The ide link already exists
            }

            // Add a "Hide" link since it does not already exist
            const caretMenu = post.find("div.story-caret-menu").first();
            const hideLink = $('<span><a class="addon-hide-link" href="javascript:void(0)">Hide</a> &nbsp; </span>');
            hideLink.click(() => {
                if (post.hasClass("classifieds-single-item-content")) {
                    const postId = post.parent().attr("id").substring(2);
                    $.post(`/api/classifieds/${postId}/hide`); // tell the server to hide this post
                }
                else {
                    const postId = post.attr("id").substring(2);
                    $.post("/ajax/confirm_mute_post/", `post_id=${postId}`); // tell the server to hide this post
                }

                post.remove(); // hide the post now
            });
            hideLink.insertBefore(caretMenu);
        });
    }

    // CSS styles to hide ads
    let styleTag = "";
    if (enableHidePaidAds) {
        styleTag += " article.gam-ad-outer-container { display: none !important; } ";
    }

    if (enableHideSponsoredAds) {
        styleTag += " div.ad-wrapper, div.programmatic-promo-container { display: none !important; } ";
    }

    if (styleTag) {
        styleTag = `<style>${styleTag}</style>`;
        const head = document.querySelector("head").innerHTML += styleTag;
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址