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

Hide all annoying content in the Nextdoor News Feed and adds and instant "Hide" button to all posts that are shown. Customize with enable-flags and title phrases to hide. It seems like about 99% of the content on Nextdoor is garbage but that 1% of useful posts are gold.

目前为 2020-01-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         Nextdoor.com - Hide all annoying content in the Nextdoor.com News Feed
// @namespace    http://tampermonkey.net/
// @version      0.1.2
// @description  Hide all annoying content in the Nextdoor News Feed and adds and instant "Hide" button to all posts that are shown. Customize with enable-flags and title phrases to hide. It seems like about 99% of the content on Nextdoor is garbage but that 1% of useful posts are gold.
// @author       Lepricon
// @match        https://nextdoor.com/news_feed/
// @grant        none
// jshint esversion: 6
// ==/UserScript==

(function() {
    'use strict';

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

    const hideKeyPhrases = ["lost dog", "found dog", "lost cat", "found cat", "coyote", "handyman"]; // Hide any posts that have titles that contain any of these key phrases. Must be all lower-case.
    const hideAuthorsNames = ["The San Diego Union-Tribune"]; // 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 fixInterval = 500; // time in milliseconds
    // --- Configurations end

    const $ = window.jQuery; // Use jQuery object provided by the site

    const hideNewNeighborAnnouncements = () => {
        // Bulk announcements
        const potentialNewNeighborAnnouncements = $("h5.content-title");
        potentialNewNeighborAnnouncements.each((index, el) => {
            const potentialNewNeighborAnnouncement = $(el);
            if (potentialNewNeighborAnnouncement.text().includes(" new neighbors joined ")) {
                const container = potentialNewNeighborAnnouncement.parent().parent().parent().parent().parent();
                if (container[0].className === "post-container") {
                    container.remove();
                }
            }
        });

        // Single announcements
        const allPosts = $("article.post-container");
        allPosts.each((index, el) => {
            const post = $(el);
            const postTitle = post.attr("aria-label").toLowerCase();
            if (postTitle.includes(" joined ")) {
                const reactionText = post.find("span[data-testid='reaction-button-text']").first().text().toLowerCase();
                if (reactionText === "welcome") {
                    post.remove();
                }
            }
        });
    }

    const hideNonFreeClassifiedAds = () => {
        // Single ads
        let classifiedAds = $("div.classifieds-single-item-content");
        classifiedAds.each((index, el) => {
            const classifiedAd = $(el);
            const price = classifiedAd.find("span.classified-single-item-content-price").first().text().toLowerCase();
            if (price === "free") {
                return;
            }

            const container = classifiedAd.parent();
            container.remove();
        });

        // Rollup ads
        classifiedAds = $("div.classified-author-rollup-slider");
        classifiedAds.each((index, el) => {
            const classifiedAd = $(el);
            const price = classifiedAd.find("span.classified-rollup-card-price").first().text().toLowerCase();
            if (price === "free") {
                return;
            }

            const container = classifiedAd.parent();
            container.remove();
        });
    }

    const hidePostsWithKeyPhrases = (phrases) => {
        const allPosts = $("article.post-container");
        allPosts.each((index, el) => {
            const post = $(el);
            const postTitle = post.attr("aria-label").toLowerCase();
            for (let i = 0; i < phrases.length; i++) {
                const phrase = phrases[i];
                if (postTitle.includes(phrase)) {
                    post.remove();
                }
            }
        });
    }

    const hidePostsByAuthors = (authors) => {
        const allAuthors = $("a.author-name");
        allAuthors.each((index, el) => {
            const authorLink = $(el);
            const authorName = authorLink.text();
            for (let i = 0; i < authors.length; i++) {
                const author = authors[i];
                if (author === authorName) {
                    const container = authorLink.parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().parent();
                    container.remove();
                }
            }
         });
    }

    const addHideLinkToPosts = () => {
        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");
            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);
        });
    }

    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>`;
        $("html > head").append($(styleTag));
    }

    const fixNextDoor = () => {
        if (enableHideNewNeighborAnnouncements) {
            hideNewNeighborAnnouncements();
        }

        if (enableHideNonFreeClassifiedAds) {
            hideNonFreeClassifiedAds();
        }

        if (hideKeyPhrases && hideKeyPhrases.length > 0) {
            hidePostsWithKeyPhrases(hideKeyPhrases);
        }

        if (hideAuthorsNames && hideAuthorsNames.length > 0) {
            hidePostsByAuthors(hideAuthorsNames);
        }

        if (enableAddHideLinkToPosts) {
            addHideLinkToPosts();
        }
    }

    const timer = setTimeout(fixNextDoor, 250); // Force a single initial fix after a short wait
    const interval = setInterval(fixNextDoor, fixInterval); // Keep calling fixNextDoor to fix content shortly after it loads
})();