Google Search sidebar

Add a sidebar on Google search for easier information filtering

目前為 2025-02-12 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Google Search sidebar
// @namespace    http://tampermonkey.net/
// @version      0.1.3
// @description  Add a sidebar on Google search for easier information filtering
// @author       Kamiya Minoru
// @match        https://www.google.com/*
// @match        https://www.google.com.tw/*
// @match        https://www.google.co.jp/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const userLang = navigator.language || navigator.userLanguage;

    const i18n = {
        'zh-TW': {
            languageSection: '語言過濾',
            timeSection: '時間過濾',
            advancedSearch: '進階搜尋',
            languages: {
                any: '不限語言搜尋',
                zhTW: '以繁體中文搜尋',
                zh: '以中文搜尋',
                ja: '以日文搜尋',
                en: '以英文搜尋'
            },
            times: {
                any: '不限時間',
                hour: '過去1小時',
                day: '過去24小時',
                week: '過去7天',
                month: '過去1個月',
                months3: '過去3個月',
                year: '過去1年'
            }
        },
        'ja': {
            languageSection: '言語フィルター',
            timeSection: '期間フィルター',
            advancedSearch: '検索オプション',
            languages: {
                any: '言語指定なし',
                zhTW: '繁体中国語で検索',
                zh: '中国語で検索',
                ja: '日本語で検索',
                en: '英語で検索'
            },
            times: {
                any: '期間指定なし',
                hour: '1時間以内',
                day: '24時間以内',
                week: '1週間以内',
                month: '1か月以内',
                months3: '3か月以内',
                year: '1年以内'
            }
        },
        'en': {
            languageSection: 'Language Filter',
            timeSection: 'Time Filter',
            advancedSearch: 'Advanced Search',
            languages: {
                any: 'Any Language',
                zhTW: 'Traditional Chinese Only',
                zh: 'Chinese Only',
                ja: 'Japanese Only',
                en: 'English Only'
            },
            times: {
                any: 'Any Time',
                hour: 'Past Hour',
                day: 'Past 24 Hours',
                week: 'Past Week',
                month: 'Past Month',
                months3: 'Past 3 Months',
                year: 'Past Year'
            }
        }
    };

    function getLocale() {
        if (i18n[userLang]) {
            return i18n[userLang];
        }

        const primaryLang = userLang.split('-')[0];
        if (i18n[primaryLang]) {
            return i18n[primaryLang];
        }

        return i18n['en'];
    }

    const locale = getLocale();

    const languageFilters = [
        { text: locale.languages.any, param: '' },
        { text: locale.languages.zhTW, param: '&lr=lang_zh-TW' },
        { text: locale.languages.zh, param: '&lr=lang_zh' },
        { text: locale.languages.ja, param: '&lr=lang_ja' },
        { text: locale.languages.en, param: '&lr=lang_en' }
    ];

    const timeFilters = [
        { text: locale.times.any, param: '' },
        { text: locale.times.hour, param: '&tbs=qdr:h' },
        { text: locale.times.day, param: '&tbs=qdr:d' },
        { text: locale.times.week, param: '&tbs=qdr:w' },
        { text: locale.times.month, param: '&tbs=qdr:m' },
        { text: locale.times.months3, param: '&tbs=qdr:m3' },
        { text: locale.times.year, param: '&tbs=qdr:y' }
    ];

    function createAdvancedSearchLink() {
        const link = document.createElement('a');
        link.textContent = locale.advancedSearch;
        link.href = getAdvancedSearchUrl();
        link.style.cssText = `
            display: block;
            color: #1a73e8;
            text-decoration: none;
            font-size: 13px;
            padding: 8px;
            margin-top: 10px;
            background: #f8f9fa;
            border-radius: 8px;
            text-align: center;
            transition: background-color 0.2s;
        `;
        link.addEventListener('mouseover', () => {
            link.style.backgroundColor = '#e8f0fe';
        });
        link.addEventListener('mouseout', () => {
            link.style.backgroundColor = '#f8f9fa';
        });
        return link;
    }

    function createFilterSection(title, filters) {
        const section = document.createElement('div');
        section.style.cssText = `
            margin: 10px 0;
            padding: 8px;
            background: #f8f9fa;
            border-radius: 8px;
        `;

        const titleElement = document.createElement('h3');
        titleElement.textContent = title;
        titleElement.style.cssText = `
            margin: 0 0 8px 0;
            font-size: 13px;
            color: #202124;
            font-weight: 500;
        `;
        section.appendChild(titleElement);

        const linkContainer = document.createElement('div');
        linkContainer.style.cssText = `
            display: flex;
            flex-direction: column;
            gap: 6px;
        `;

        filters.forEach(filter => {
            const link = document.createElement('a');
            link.textContent = filter.text;
            link.href = getCurrentUrlWithParam(filter.param);
            link.style.cssText = `
                color: #1a73e8;
                text-decoration: none;
                font-size: 12px;
                padding: 3px 6px;
                border-radius: 4px;
                transition: background-color 0.2s;
            `;
            link.addEventListener('mouseover', () => {
                link.style.backgroundColor = '#e8f0fe';
            });
            link.addEventListener('mouseout', () => {
                link.style.backgroundColor = 'transparent';
            });
            linkContainer.appendChild(link);
        });

        section.appendChild(linkContainer);
        return section;
    }

    function getCurrentUrlWithParam(param) {
        const url = new URL(window.location.href);

        // Remove existing language or time parameters
        url.searchParams.delete('lr');
        url.searchParams.delete('tbs');

        // Add new parameters
        if (param) {
            const cleanParam = param.startsWith('&') ? param.substring(1) : param;
            const [key, value] = cleanParam.split('=');
            url.searchParams.set(key, value);
        }

        return url.toString();
    }

    function getAdvancedSearchUrl() {
        const url = new URL('https://www.google.com/advanced_search');
        const currentParams = new URLSearchParams(window.location.search);

        // Append current search parameters to the advanced search URL
        currentParams.forEach((value, key) => {
            url.searchParams.set(key, value);
        });

        return url.toString();
    }

    function addFiltersToPage() {
        const searchResults = document.getElementById('search');
        if (!searchResults) return;

        const sidebar = document.createElement('div');
        sidebar.style.cssText = `
            position: fixed;
            top: 10px;
            left: 10px;
            width: 160px;
            z-index: 1000;
        `;

        sidebar.appendChild(createFilterSection(locale.languageSection, languageFilters));
        sidebar.appendChild(createFilterSection(locale.timeSection, timeFilters));
        sidebar.appendChild(createAdvancedSearchLink());

        document.body.appendChild(sidebar);
    }

    const observer = new MutationObserver((mutations, obs) => {
        const searchResults = document.getElementById('search');
        if (searchResults) {
            addFiltersToPage();
            obs.disconnect();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();