Google Search Enhancer

Adds a collapsible Google Enhancer panel with optional AI result filter, new tab option, and saved links with better star placement and popup UI.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name Google Search Enhancer
// @namespace https://codymkw.nekoweb.org/
// @version 4.4
// @description Adds a collapsible Google Enhancer panel with optional AI result filter, new tab option, and saved links with better star placement and popup UI.
// @author Cody
// @match https://www.google.com/search*
// @match https://www.google.*.*/search*
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// ==/UserScript==
(function () {
    'use strict';
    const STORAGE_KEY = 'googleEnhancerSettings';
    const LINKS_KEY = 'googleSavedLinks';
    const DEFAULT_SETTINGS = {
        hideAI: true,
        newTab: false
    };
    const loadSettings = () => JSON.parse(GM_getValue(STORAGE_KEY, JSON.stringify(DEFAULT_SETTINGS)));
    const saveSettings = (settings) => GM_setValue(STORAGE_KEY, JSON.stringify(settings));
    const settings = loadSettings();
    const loadLinks = () => JSON.parse(GM_getValue(LINKS_KEY, '[]'));
    const saveLinks = (links) => GM_setValue(LINKS_KEY, JSON.stringify(links));
    // === HANDLE FILTER ===
    const params = new URLSearchParams(window.location.search);
    const q = params.get('q');
    // If hideAI enabled, ensure "-ai" is added
    if (settings.hideAI && q && !q.toLowerCase().includes('-ai')) {
        params.set('q', `${q} -ai`);
        window.location.replace(`${window.location.pathname}?${params.toString()}`);
        return;
    }
    // If hideAI disabled but "-ai" is in query, remove it
    if (!settings.hideAI && q && q.toLowerCase().includes('-ai')) {
        const cleaned = q.replace(/\s*-ai\s*/gi, ' ').trim();
        params.set('q', cleaned);
        window.location.replace(`${window.location.pathname}?${params.toString()}`);
        return;
    }
    // === NEW TAB OPTION ===
    if (settings.newTab) {
        const observer = new MutationObserver(() => {
            document.querySelectorAll('a').forEach(a => {
                if (a.href && !a.target) a.target = '_blank';
            });
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }
    // === ADD SAVE ICONS TO RESULTS ===
    const addSaveIcons = () => {
        document.querySelectorAll('a h3').forEach(h3 => {
            const link = h3.closest('a');
            if (!link || h3.dataset.hasSaveIcon) return;
            h3.dataset.hasSaveIcon = true;
            const wrapper = document.createElement('span');
            wrapper.style.position = 'relative';
            wrapper.style.display = 'inline-block';
            h3.parentNode.insertBefore(wrapper, h3);
            wrapper.appendChild(h3);
            const star = document.createElement('span');
            star.textContent = '⭐';
            star.style.cursor = 'pointer';
            star.style.position = 'absolute';
            star.style.right = '-24px';
            star.style.top = '2px';
            star.style.opacity = '0.6';
            star.style.fontSize = '16px';
            star.title = 'Save for later';
            star.style.transition = 'opacity 0.2s, transform 0.2s';
            const savedLinks = loadLinks();
            if (savedLinks.find(l => l.url === link.href)) {
                star.style.opacity = '1';
                star.style.filter = 'drop-shadow(0 0 2px gold)';
            }
            star.addEventListener('mouseenter', () => {
                star.style.transform = 'scale(1.2)';
            });
            star.addEventListener('mouseleave', () => {
                star.style.transform = 'scale(1)';
            });
            star.addEventListener('click', (e) => {
                e.stopPropagation();
                e.preventDefault();
                const links = loadLinks();
                const existing = links.find(l => l.url === link.href);
                if (existing) {
                    saveLinks(links.filter(l => l.url !== link.href));
                    star.style.opacity = '0.6';
                    star.style.filter = 'none';
                } else {
                    links.push({ title: h3.innerText, url: link.href });
                    saveLinks(links);
                    star.style.opacity = '1';
                    star.style.filter = 'drop-shadow(0 0 2px gold)';
                }
            });
            wrapper.appendChild(star);
        });
    };
    const resultObserver = new MutationObserver(addSaveIcons);
    resultObserver.observe(document.body, { childList: true, subtree: true });
    addSaveIcons();
    // === CONTROL PANEL ===
    const panel = document.createElement('div');
    panel.innerHTML = `
        <div id="ge-toggle" style="
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: var(--bg);
            color: var(--text);
            border: 1px solid var(--border);
            border-radius: 50%;
            width: 40px;
            height: 40px;
            text-align: center;
            line-height: 38px;
            font-size: 18px;
            cursor: pointer;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            z-index: 99999;
        ">⚙️</div>
        <div id="ge-panel" style="
            position: fixed;
            bottom: 70px;
            right: 20px;
            z-index: 9999;
            background: var(--bg);
            color: var(--text);
            border: 1px solid var(--border);
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            padding: 10px 12px;
            font-family: sans-serif;
            font-size: 13px;
            min-width: 190px;
            display: none;
        ">
            <strong>⚙️ Google Enhancer</strong>
            <div style="margin-top: 8px;">
                <label><input type="checkbox" id="hideAI" ${settings.hideAI ? 'checked' : ''}> Hide AI Results</label><br>
                <label><input type="checkbox" id="newTab" ${settings.newTab ? 'checked' : ''}> Open results in new tab</label>
            </div>
            <button id="showSavedLinks" style="
                margin-top: 10px;
                width: 100%;
                border: none;
                border-radius: 6px;
                padding: 6px;
                background: var(--button-bg);
                color: var(--button-text);
                cursor: pointer;
            ">📁 Saved Links</button>
        </div>
    `;
    document.body.appendChild(panel);
    // === DARK/LIGHT THEME ===
    const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    const vars = dark
        ? { bg: '#222', text: '#eee', border: '#555', buttonBg: '#333', buttonText: '#fff' }
        : { bg: '#fff', text: '#222', border: '#ccc', buttonBg: '#f5f5f5', buttonText: '#000' };
    for (const [k, v] of Object.entries(vars)) {
        panel.querySelector('#ge-panel').style.setProperty(`--${k}`, v);
        panel.querySelector('#ge-toggle').style.setProperty(`--${k}`, v);
    }
    // === TOGGLE PANEL ===
    const toggleBtn = document.getElementById('ge-toggle');
    const mainPanel = document.getElementById('ge-panel');
    toggleBtn.addEventListener('click', () => {
        mainPanel.style.display = mainPanel.style.display === 'none' ? 'block' : 'none';
    });
    // === SETTINGS ===
    ['hideAI', 'newTab'].forEach(key => {
        document.getElementById(key).addEventListener('change', e => {
            settings[key] = e.target.checked;
            saveSettings(settings);
            location.reload();
        });
    });
    // === SAVED LINKS POPUP ===
    const makeSavedPopup = () => {
        const popup = document.createElement('div');
        popup.innerHTML = `
            <div id="savedLinksPopup" style="
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: ${vars.bg};
                color: ${vars.text};
                border: 1px solid ${vars.border};
                border-radius: 10px;
                box-shadow: 0 4px 20px rgba(0,0,0,0.4);
                padding: 15px;
                z-index: 100000;
                width: 320px;
                max-height: 400px;
                overflow-y: auto;
            ">
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <strong>📁 Saved Links</strong>
                    <button id="closePopup" style="
                        background: transparent;
                        color: ${vars.text};
                        border: none;
                        font-size: 16px;
                        cursor: pointer;
                    ">❎</button>
                </div>
                <div id="linksContainer" style="margin-top: 10px;"></div>
                <button id="clearAllLinks" style="
                    margin-top: 10px;
                    width: 100%;
                    border: none;
                    border-radius: 6px;
                    padding: 6px;
                    background: ${vars.buttonBg};
                    color: ${vars.buttonText};
                    cursor: pointer;
                ">🗑️ Clear All</button>
            </div>
        `;
        document.body.appendChild(popup);
        const container = popup.querySelector('#linksContainer');
        const links = loadLinks();
        if (links.length === 0) {
            container.innerHTML = `<p style="opacity: 0.7;">No saved links yet.</p>`;
        } else {
            links.forEach((l, i) => {
                const div = document.createElement('div');
                div.style.marginBottom = '6px';
                div.innerHTML = `
                    <a href="${l.url}" target="_blank" style="color: ${vars.text}; text-decoration: none;">${l.title}</a>
                    <button data-index="${i}" style="margin-left: 8px; font-size: 12px;">❌</button>
                `;
                container.appendChild(div);
            });
            container.querySelectorAll('button[data-index]').forEach(btn => {
                btn.addEventListener('click', e => {
                    const idx = +e.target.dataset.index;
                    const links = loadLinks();
                    links.splice(idx, 1);
                    saveLinks(links);
                    popup.remove();
                    makeSavedPopup();
                });
            });
        }
        popup.querySelector('#clearAllLinks').addEventListener('click', () => {
            if (confirm('Clear all saved links?')) {
                saveLinks([]);
                popup.remove();
            }
        });
        popup.querySelector('#closePopup').addEventListener('click', () => {
            popup.remove();
        });
    };
    document.getElementById('showSavedLinks').addEventListener('click', makeSavedPopup);
})();