Google Additional Search Filters

This script shows a bunch filters above the default Google search filters that helps you easily search sites (directly or with Google) and subreddits

// ==UserScript==
// @name               Google Additional Search Filters
// @namespace          https://greasyfork.org/en/users/10118-drhouse
// @version            9.0
// @description        This script shows a bunch filters above the default Google search filters that helps you easily search sites (directly or with Google) and subreddits
// @run-at             document-start
// @include            https://*.google.*/*
// @include            https://www.google.*/search*
// @exclude            https://www.google.com/maps/*
// @exclude            https://www.google.com/flights/*
// @require            http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @require            https://greasyfork.org/scripts/439099-monkeyconfig-modern-reloaded/code/MonkeyConfig%20Modern%20Reloaded.js?version=1012538
// @grant              GM_getValue
// @grant              GM_setValue
// @grant              GM_addStyle
// @grant              GM_info
// @grant              GM_registerMenuCommand
// @author             drhouse (updated by Gemini AI & User)
// @license            CC-BY-NC-SA-4.0
// @icon               https://www.google.com/s2/favicons?domain=google.com
// ==/UserScript==
/* global jQuery, MonkeyConfig, $ */

$(document).ready(function () {
    // --- SVG Icon Definitions ---
    const searchIconSvg = '<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width:16px; height:16px; fill:currentColor;"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>';
    const clearIconSvg = '<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width:18px; height:18px; fill:currentColor;"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>';

    // --- Configuration (Simplified) ---
    var cfg = new MonkeyConfig({
        title: 'Configure Additional Search Filters',
        menuCommand: true,
        params: {
            'YouTube': { type: 'checkbox', default: true, label: 'Enable YouTube Filter (Main)' },
            'Marginalia': { type: 'checkbox', default: true, label: 'Enable Marginalia Filter (Main)' },
            'Yandex': { type: 'checkbox', default: true, label: 'Enable Yandex Filter (Main)' },
            'Bing': { type: 'checkbox', default: true, label: 'Enable Bing Filter (Main)' },
            'ChatGPT': { type: 'checkbox', default: true, label: 'Enable ChatGPT Filter (Main)' },
            'Perplexity': { type: 'checkbox', default: true, label: 'Enable Perplexity Filter (Main)' },
            'Google News': { type: 'checkbox', default: true, label: 'Enable Google News Filter (Main)' },
            'Wikipedia': { type: 'checkbox', default: true, label: 'Enable Wikipedia Filter (Main)' },
            'StackExchange': { type: 'checkbox', default: true, label: 'Enable StackExchange Filter (Main)' },
            'Substack': { type: 'checkbox', default: true, label: 'Enable Substack Filter (Main)' },
            'Reddit_Enable': { type: 'checkbox', default: true, label: 'Enable Reddit Filter (Main, with Subreddits)'},
            'Reddit_Subreddits': {
                type: 'text',
                default: 'explainlikeimfive,askscience,NoStupidQuestions,techsupport,LifeProTips',
                label: 'Subreddits for Dropdown (comma-separated, Google search)'
            },
            'CustomSites_Enable': { type: 'checkbox', default: true, label: 'Enable Custom Site Filters (in "More")'},
            'CustomSites_List': {
                type: 'text',
                default: 'medium.com,quora.com,github.com,dev.to',
                label: 'Custom Site Domains (comma-separated, Google search)'
            },
            'OpenInNewTab': {
                type: 'checkbox',
                default: false,
                label: 'Open filter links in a new tab'
            }
        },
    });

    // --- Helper Functions ---
    function getCleanQuery() {
        let currentQuery = '';
        const queryInputs = ["input[name='q'][type='text']", "textarea[name='q']", "textarea[aria-label='Search']", "textarea[title='Search']"];
        for (const selector of queryInputs) {
            const inputElement = $(selector);
            if (inputElement.length > 0 && inputElement.val() && inputElement.val().trim() !== '') {
                currentQuery = inputElement.val();
                break;
            }
        }
        if (!currentQuery && window.location.search.includes("q=")) {
            try {
                const params = new URLSearchParams(window.location.search);
                currentQuery = params.get('q') || '';
            } catch (e) {
                const match = window.location.search.match(/[?&]q=([^&]+)/);
                if (match) currentQuery = decodeURIComponent(match[1].replace(/\+/g, ' '));
            }
        }
        const siteFilterRegex = /\b(site:\S+|inurl:\S+|-site:\S+)\s*/gi;
        let cleaned = (currentQuery || '').replace(siteFilterRegex, ' ').trim();
        cleaned = cleaned.replace(/\s\s+/g, ' ');
        return cleaned;
    }

    const cleanQuery = getCleanQuery();
    if (!cleanQuery) { return; }

    const googleHostname = $(location).attr('hostname');
    const openInNewTab = cfg.get('OpenInNewTab');
    const linkTarget = openInNewTab ? '_blank' : '_self';

    // --- Container & Placement ---
    let customFiltersMainContainer = $('#gm-custom-filters-main-container');
    if (customFiltersMainContainer.length > 0) { customFiltersMainContainer.empty(); }
    else { customFiltersMainContainer = $('<div id="gm-custom-filters-main-container" style="display: inline-flex; align-items: center; white-space: nowrap; vertical-align: top;"></div>'); }

    const placementTarget = $('.rfiSsc.YNk70c').first();
    if (placementTarget.length > 0) {
        placementTarget.after(customFiltersMainContainer);
        customFiltersMainContainer.css({ 'margin-top': '1px', 'padding': '0 106px' });
    } else {
        let googleFiltersBar = $('#hdtb-msb').first();
        if (googleFiltersBar.length > 0) { googleFiltersBar.append(customFiltersMainContainer); }
        else {
             let lastGoogleTab = $('.hdtb-mitem').last();
             if(lastGoogleTab.length === 0) lastGoogleTab = $('[role="tablist"] a').last();
             if (lastGoogleTab.length > 0) { customFiltersMainContainer.insertAfter(lastGoogleTab); }
             else {
                 const fallbackTarget = $(".crJ18e, #searchform").first();
                 if (fallbackTarget.length > 0) { customFiltersMainContainer.insertAfter(fallbackTarget); }
                 else { console.error("Google ASF: Cannot find any insertion point."); return; }
             }
        }
    }

    // --- Element Creation Helpers ---
    function createSimpleFilterLinkElement(siteName, siteUrl, iconDomain) {
        const faviconUrl = `https://www.google.com/s2/favicons?domain=${encodeURIComponent(iconDomain)}&sz=16`;
        const linkElement = $(`<a class="gm-custom-filter-link LatpMc nPDzT T3FoJb" href="${siteUrl}" target="${linkTarget}"></a>`);
        if(openInNewTab) linkElement.attr('rel', 'noopener noreferrer');
        linkElement.append(`<img src="${faviconUrl}" class="gm-filter-icon" alt="" onerror="this.style.display='none'">`);
        linkElement.append(`<div class="gm-filter-label">${siteName}</div>`);
        return linkElement;
    }

    function createMagnifierFilterElement(siteDef, mainUrl, altUrl, altTitle) {
        const element = $('<div class="gm-filter-item LatpMc nPDzT T3FoJb"></div>');
        const mainLink = $(`<a class="gm-main-action" href="${mainUrl}" target="${linkTarget}"></a>`);
        if (openInNewTab) mainLink.attr('rel', 'noopener noreferrer');
        mainLink.append(`<img src="https://www.google.com/s2/favicons?domain=${encodeURIComponent(siteDef.iconDomain)}&sz=16" class="gm-filter-icon" alt="" onerror="this.style.display='none'">`);
        mainLink.append(`<div class="gm-filter-label">${siteDef.displayName}</div>`);
        element.append(mainLink);

        const altLink = $(`<a class="gm-alt-search-action" href="${altUrl}" target="${linkTarget}" title="${altTitle}"></a>`);
        if (openInNewTab) altLink.attr('rel', 'noopener noreferrer');
        altLink.html(searchIconSvg);
        element.append(altLink);
        return element;
    }

    // --- Clear Filter Button ---
    const clearFilterUrl = `https://${googleHostname}/search?q=${encodeURIComponent(cleanQuery)}`;
    const clearButton = $(`<a class="gm-clear-filter-button LatpMc nPDzT T3FoJb" href="${clearFilterUrl}" target="${linkTarget}" title="Clear filters and search Google"></a>`);
    if(openInNewTab) clearButton.attr('rel', 'noopener noreferrer');
    clearButton.html(clearIconSvg);
    customFiltersMainContainer.append(clearButton);

    // --- Site Definitions with Fixed Behavior ---
    const siteDefinitions = [
        // Group 1: Google Primary, Direct via Magnifier
        { configKey: 'YouTube',       displayName: 'YT',       iconDomain: 'youtube.com',       directBaseUrl: 'https://www.youtube.com/results?search_query=', siteSearchTerm: 'youtube.com',       searchType: 'google_primary' },
        { configKey: 'Wikipedia',     displayName: 'Wiki',     iconDomain: 'wikipedia.org',     directBaseUrl: 'https://en.wikipedia.org/wiki/Special:Search?search=', siteSearchTerm: 'wikipedia.org',   searchType: 'google_primary' },
        { configKey: 'StackExchange', displayName: 'SE',       iconDomain: 'stackexchange.com', directBaseUrl: 'https://stackexchange.com/search?q=', siteSearchTerm: 'stackexchange.com', searchType: 'google_primary' },
        { configKey: 'Substack',      displayName: 'Substack', iconDomain: 'substack.com',      directBaseUrl: 'https://substack.com/search/', siteSearchTerm: 'substack.com',      searchType: 'google_primary' }, // No reliable direct search, so magnifier won't be created

        // Group 2: Always Direct, No Magnifier
        { configKey: 'Google News',   displayName: 'GN',       iconDomain: 'news.google.com',   directBaseUrl: 'https://news.google.com/search?q=', siteSearchTerm: null, searchType: 'direct_only' },
        { configKey: 'ChatGPT',       displayName: 'CG',       iconDomain: 'chatgpt.com',       directBaseUrl: 'https://chatgpt.com/?q=', siteSearchTerm: null, searchType: 'direct_only' },
        { configKey: 'Marginalia',    displayName: 'MR',       iconDomain: 'marginalia.nu',     directBaseUrl: 'https://marginalia-search.com/search?query=', siteSearchTerm: null, searchType: 'direct_only' },
        { configKey: 'Yandex',        displayName: 'YD',       iconDomain: 'yandex.com',        directBaseUrl: 'https://yandex.com/search/?text=', siteSearchTerm: null, searchType: 'direct_only' },
        { configKey: 'Bing',          displayName: 'BG',       iconDomain: 'bing.com',          directBaseUrl: 'https://www.bing.com/search?q=', siteSearchTerm: null, searchType: 'direct_only' },
        { configKey: 'Perplexity',    displayName: 'PR',       iconDomain: 'perplexity.ai',     directBaseUrl: 'https://www.perplexity.ai/search?q=', siteSearchTerm: null, searchType: 'direct_only' },
    ];

    const mainDisplayKeys = ['YouTube', 'Google News', 'Wikipedia', 'ChatGPT', 'Marginalia', 'Yandex', 'Bing', 'Perplexity', 'StackExchange', 'Substack'];
    const overflowLinkElements = [];

    // --- Generate Filter Links/Items ---
    siteDefinitions.forEach(siteDef => {
        if (!cfg.get(siteDef.configKey)) { return; }

        let element;
        switch(siteDef.searchType) {
            case 'google_primary':
                let googleSearchUrl = `https://${googleHostname}/search?q=site%3A${encodeURIComponent(siteDef.siteSearchTerm)}+${encodeURIComponent(cleanQuery)}`;
                if (siteDef.configKey === 'YouTube') { googleSearchUrl += '&tbm=vid'; }
                const directSearchUrl = siteDef.directBaseUrl ? siteDef.directBaseUrl + encodeURIComponent(cleanQuery) : null;
                if (directSearchUrl) {
                    element = createMagnifierFilterElement(siteDef, googleSearchUrl, directSearchUrl, `Search directly on ${siteDef.iconDomain}`);
                } else { // Fallback for sites like Substack with no reliable direct search
                    element = createSimpleFilterLinkElement(siteDef.displayName, googleSearchUrl, siteDef.iconDomain);
                }
                break;
            case 'direct_only':
                const directUrl = siteDef.directBaseUrl + encodeURIComponent(cleanQuery);
                element = createSimpleFilterLinkElement(siteDef.displayName, directUrl, siteDef.iconDomain);
                break;
        }

        if (element) {
            if (mainDisplayKeys.includes(siteDef.configKey)) {
                customFiltersMainContainer.append(element);
            } else {
                overflowLinkElements.push(element);
            }
        }
    });

    // --- Reddit Special Handling (Group 1 Behavior) ---
    if (cfg.get('Reddit_Enable')) {
        const mainRedditUrl = `https://${googleHostname}/search?q=${encodeURIComponent(`site:reddit.com ${cleanQuery}`)}`;
        const altRedditUrl = `https://www.reddit.com/search/?q=${encodeURIComponent(cleanQuery)}`;
        const redditElement = createMagnifierFilterElement({displayName: 'RT', iconDomain: 'reddit.com'}, mainRedditUrl, altRedditUrl, 'Search directly on Reddit');
        redditElement.addClass('gm-reddit-main-container');

        let subredditsString = cfg.get('Reddit_Subreddits') || '';
        if (subredditsString.trim() !== '') {
            const subreddits = subredditsString.split(',').map(sr => sr.trim().replace(/^(r\/)/i, '')).filter(Boolean);
            if (subreddits.length > 0) {
                const redditDropdown = $('<div class="gm-reddit-dropdown"></div>');
                subreddits.forEach(srName => {
                    const subredditUrl = `https://${googleHostname}/search?q=${encodeURIComponent(`site:reddit.com/r/${srName} ${cleanQuery}`)}`;
                    const subredditLinkEl = $(`<a href="${subredditUrl}" class="gm-more-dropdown-item" target="${linkTarget}">r/${srName}</a>`);
                    if(openInNewTab) subredditLinkEl.attr('rel', 'noopener noreferrer');
                    redditDropdown.append(subredditLinkEl);
                });
                redditElement.append(redditDropdown);
                redditElement.find('.gm-main-action').on('mouseenter', () => redditDropdown.stop(true, true).fadeIn(100));
                redditElement.on('mouseleave', () => redditDropdown.stop(true, true).fadeOut(200));
                redditDropdown.on('mouseenter', function() { $(this).stop(true, true).show(); });
            }
        }
        customFiltersMainContainer.append(redditElement);
    }

    // --- Custom Site Filters (Group 3 Behavior: Always Google) ---
    if (cfg.get('CustomSites_Enable')) {
        let customSitesRaw = cfg.get('CustomSites_List') || '';
        if (customSitesRaw.trim() !== '') {
            customSitesRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean).forEach(domain => {
                const name = (domain.split('.')[0] || domain).replace(/^\w/, c => c.toUpperCase());
                const searchUrl = `https://${googleHostname}/search?q=site%3A${encodeURIComponent(domain)}+${encodeURIComponent(cleanQuery)}`;
                overflowLinkElements.push(createSimpleFilterLinkElement(name, searchUrl, domain));
            });
        }
    }

    // --- "More" Dropdown ---
    if (overflowLinkElements.length > 0) {
        const moreBtnContainer = $('<div id="gm-more-filters-btn-container" class="LatpMc nPDzT T3FoJb" style="position:relative; cursor:default; padding: 0 8px;"></div>');
        moreBtnContainer.append(`<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gm-filter-icon" style="fill:currentColor; width:20px; height:20px; margin:0;"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></svg>`);
        const moreDropdown = $('<div id="gm-more-filters-dropdown"></div>');
        overflowLinkElements.forEach(el => {
            el.removeClass('LatpMc nPDzT T3FoJb');
            if (el.hasClass('gm-filter-item')) { // Complex item with magnifier
                 el.addClass('gm-more-dropdown-item').find('.gm-alt-search-action').hide(); // Hide magnifier in dropdown
                 el.find('.gm-main-action').css({'padding':'8px 16px', 'width':'100%'});
            } else { // Simple link
                 el.addClass('gm-more-dropdown-item');
            }
            moreDropdown.append(el);
        });
        moreBtnContainer.append(moreDropdown);
        customFiltersMainContainer.append(moreBtnContainer);
        moreBtnContainer.on('mouseenter', () => moreDropdown.stop(true, true).fadeIn(100)).on('mouseleave', () => moreDropdown.stop(true, true).fadeOut(200));
    }

    // --- Cleanup ---
    if (customFiltersMainContainer.children('.LatpMc').length <= 1) { customFiltersMainContainer.remove(); }

    // --- Styles ---
    GM_addStyle(`
        #gm-custom-filters-main-container { margin-left: 8px; }
        .gm-filter-item.LatpMc, .gm-custom-filter-link.LatpMc, .gm-clear-filter-button.LatpMc {
            margin-left: 1px; margin-right: 1px; padding: 0 10px; height: 36px;
            display: inline-flex !important; align-items: center; text-decoration: none; position: relative;
        }
        .gm-clear-filter-button.LatpMc { padding: 0 8px; margin-right: 5px; }
        .gm-filter-icon { width: 16px; height: 16px; margin-right: 5px; border-radius: 2px; }
        .gm-filter-label { vertical-align: middle; }
        .gm-main-action { display: flex; align-items: center; text-decoration: none; color: inherit; height: 100%; flex-grow: 1; }
        .gm-alt-search-action { display: inline-flex; align-items: center; justify-content: center; margin-left: 6px; padding: 3px; border-radius: 50%; cursor: pointer; width: 22px; height: 22px; color: inherit; }
        .gm-alt-search-action:hover { background-color: rgba(0,0,0,0.08); }
        .gm-alt-search-action svg { width: 15px; height: 15px; fill: currentColor; }
        #gm-more-filters-dropdown, .gm-reddit-dropdown {
            display: none; position: absolute; top: 100%; left: 0; background-color: white; border: 1px solid #dadce0;
            border-radius: 6px; z-index: 1005; min-width: 180px; max-width: 280px; max-height: 600px;
            overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 6px 0; margin-top: 2px;
        }
        .gm-more-dropdown-item, .gm-reddit-dropdown > .gm-more-dropdown-item {
            display: flex !important; align-items: center; padding: 8px 16px !important; text-decoration: none !important;
            color: #202124 !important; font-size: 14px !important; white-space: nowrap !important;
            background-color: transparent !important; margin: 0 !important; height: auto !important;
        }
        .gm-more-dropdown-item:hover { background-color: #f1f3f4 !important; }
        .gm-more-dropdown-item .gm-filter-icon { margin-right: 8px !important; }
        .gm-reddit-dropdown a.gm-more-dropdown-item { padding-left: 24px !important; color: #202124 !important; }
    `);
});