Greasy Fork is available in English.

Carousell++

Makes Carousell search less annoying and more useful

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Carousell++
// @namespace    http://tampermonkey.net/
// @version      1.1
// @license      GPL-3.0
// @description  Makes Carousell search less annoying and more useful
// @author       oopsitsdeleted
// @match        https://www.carousell.sg/search/*
// @match        https://www.carousell.com.my/search/*
// @match        https://www.carousell.ph/search/*
// @match        https://www.carousell.com.hk/search/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Log removal events only if you want to debug.
    const DEBUG = true;

    /**
     * removes all the annoying bs from carousell search (boosted/sponsored) hehe 99.999% vibecoded
     */
    function removeSponsoredPosts() {
        let removedCount = 0;

        // --- boosted/spotlight ads ---

        // find boosted
        const boostedIndicators = document.querySelectorAll('svg[fill="#00816D"]');

        // find spotlight indicators
        const spotlightIndicators = document.querySelectorAll('div[style*="background: rgb(169, 80, 168)"]');

        // Combine all listing indicators into one NodeList
        const allListingIndicators = [...boostedIndicators, ...spotlightIndicators];

        allListingIndicators.forEach(indicatorElement => {
            let cardElement = indicatorElement;
            let depth = 0;
            const MAX_DEPTH = 10;

            // main card wrapper
            while (cardElement && depth < MAX_DEPTH) {
                // rabs
                if (cardElement.hasAttribute('data-testid') && cardElement.getAttribute('data-testid').startsWith('listing-card')) {

                    // remove parents
                    const removalTarget = cardElement.parentElement;

                    if (removalTarget && removalTarget.parentElement) {
                        if (DEBUG) console.log(`[Carousell++] Removing Boosted/Spotlight Listing:`, removalTarget);
                        removalTarget.parentElement.removeChild(removalTarget);
                        removedCount++;
                        return;
                    }
                }
                cardElement = cardElement.parentElement;
                depth++;
            }
        });

        // --- banner ads ---

        // target shoutout banner
        const shoutoutSelector = '.D_bNr';
        const shoutoutBanners = document.querySelectorAll(shoutoutSelector);

        shoutoutBanners.forEach(banner => {
            // remove grid item and collapse space so it doesnt leave blank space
            // Shoutout Banner (.D_bNr) -> Parent DIV (1st level) -> Grid Item Container (2nd level, what we want to remove)
            const parent = banner.parentElement;
            const removalTarget = parent ? parent.parentElement : null;

            if (removalTarget && removalTarget.parentElement) {
                if (DEBUG) console.log(`[Carousell++] Removing Shoutout Banner Grid Container:`, removalTarget);
                removalTarget.parentElement.removeChild(removalTarget);
                removedCount++;
            }
        });

        // --- big ass carousell ads ---

        // target banner
        const smartRenderSelector = '.D_aEW';
        const smartRenderBanners = document.querySelectorAll(smartRenderSelector);

        smartRenderBanners.forEach(banner => {
            // remove grid item
            // .D_aEW -> Parent DIV (.D_ZP) -> Grid Item Container (what we want to remove)
            const removalTarget = banner.parentElement;

            if (removalTarget && removalTarget.parentElement) {
                if (DEBUG) console.log(`[Carousell++] Removing Smart Render/Carousell Ad Grid Container:`, removalTarget);
                removalTarget.parentElement.removeChild(removalTarget);
                removedCount++;
            }
        });

        // --- big big big banner ---

        // target banner
        const promotedContentSelector = '.D_ais';
        const promotedContentBanners = document.querySelectorAll(promotedContentSelector);

        promotedContentBanners.forEach(banner => {
            // hmmm
            const removalTarget = banner;

            if (removalTarget && removalTarget.parentElement) {
                if (DEBUG) console.log(`[Carousell++] Removing Promoted Content Banner:`, removalTarget);
                removalTarget.parentElement.removeChild(removalTarget);
                removedCount++;
            }
        });


        if (DEBUG && removedCount > 0) {
            console.log(`[Carousell++] Successfully removed ${removedCount} annoying bs.`);
        }
    }

    /**
     * Use MutationObserver to watch for new content being loaded (e.g., infinite scroll)
     * and run the removal function again.
     */
    function observeDOM() {
        // Observe the main content area (document.body) for new elements.
        const targetNode = document.body;

        const config = { childList: true, subtree: true };

        const observer = new MutationObserver((mutationsList, observer) => {
            // Run the removal logic every time the DOM changes.
            removeSponsoredPosts();
        });

        // Start observing the target node for configured mutations
        observer.observe(targetNode, config);

        // Run the function immediately on page load, as the observer might miss initial content
        removeSponsoredPosts();
    }

    // Start the whole process
    observeDOM();

})();