Better Google Search

Adds useful features for google searching. English support only. Tested on Brave Browser. Intended to work with light mode and dark mode. Doesn't work on mobile view.

// ==UserScript==
// @name          Better Google Search
// @namespace     Better Google Search
// @match         https://www.google.com/search?*
// @grant         GM_addStyle
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// @version       1.7
// @author        kyosukyuu
// @description   Adds useful features for google searching. English support only. Tested on Brave Browser. Intended to work with light mode and dark mode. Doesn't work on mobile view.
// @license       MIT
// ==/UserScript==

GM_addStyle (`
  .btns--container {
    display: flex;
    flex-wrap: wrap;
    position: absolute;
    width: 100%;
    left: 100%;
    top: 0;
    margin-left: 20px;
    z-index: 1;
    background: #202124;
    border-radius: 3px;
  }
  .btns--container-light {
    background: #fff;
  }
  .btn {
    margin-right: 8px;
    cursor: pointer;
  }
  .btn--container {
    position: relative;
    padding: 4px;
    margin: 0 6px;
    margin-bottom: 6px;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
  }
  .btn--container-light {
    color: #70757a !important;
  }
  .btn--container:hover {
    color: #ddd
  }
  .btn--container-light:hover {
    color: #202124 !important;
  }
/*   .btn--container:hover .caret-dropdown {
    border-color: #ddd transparent;
  }
  .btn--container-light:hover .caret-dropdown {
    border-color: #202124 transparent !important;
  }
 */
  .btn--active {
    color: #e8eaed !important;
  }
  .btn--active-light {
    color: #202124;
  }
  .caret-dropdown--active {
    border-color: #ddd transparent !important;
  }

  .btn--caret::after{
    border-color: #9aa0a6 transparent;
    border-style: solid;
    border-width: 5px 4px 0 4px;
    width: 0;
    height: 0;
    margin-left: 2px;
    top: 13px;
    margin-top: -2px;
    position: absolute;
    right: 0;
    content: " "
  }
  .btn--caret:hover::after, .btn--caret:focus::after {
    border-color: #ddd transparent !important;
  }
  .btn--container:hover > .btn--caret::after {
    border-color: #ddd transparent !important;
  }
  .btn--container-light:hover > .btn--caret::after {
    border-color: #202124 transparent !important;
  }

/*   .caret-dropdown {
    border-color: #9aa0a6 transparent;
    border-style: solid;
    border-width: 5px 4px 0 4px;
    width: 0;
    height: 0;
    margin-left: 2px;
    top: 13px;
    margin-top: -2px;
    position: absolute;
    right: 0;
  } */

  .dropdown--container {
    z-index: 10;
    padding: 5px 0;
    border-radius: 8px;
    box-shadow: 1px 1px 15px 0px #171717;
    background-color: #202124;
    position: absolute;
    width: 100%;
    min-width: 100px;
    max-width: 105px;
    max-height: 200px;
    top: 25px;
    overflow: hidden;
    overflow-y: auto;
    list-style-type: none;
  }
  .dropdown--container-light {
    box-shadow: 0 2px 10px 0 rgb(0 0 0 / 20%);
    background-color: #fff;
  }
  .show {
    display: block !important;
  }
  .hide {
    display: none;
  }

  .dropdown--items {
    line-height: 16px;
    padding: 7px 8px;
    color: #bdc1c6;
    display: flex;
    align-items: center;
    cursor: pointer;
    margin: auto;
  }
  .dropdown--items-light {
    color: #5f6368;
  }
  .dropdown--items:hover, .dropdown--items:focus {
    background-color: rgba(255,255,255,0.1);
  }
  .dropdown--items-light:hover, .dropdown--items-light:focus{
    background-color: rgba(0,0,0,0.1) !important;
  }

  .flex-break {
    flex-basis: 100%;
    height: 0;
  }

  .__MonkeyConfig_overlay {
    width: 100% !important;
    height: 100% !important;
  }
  .__MonkeyConfig_layer {
    left: calc(50% - 165px) !important;
    top: calc(50% - 60.25px) !important;
    width: 330px;
    height: 120.50px;
  }
  #__MonkeyConfig_frame {
    width: 100%;
  }

  @media only screen and (max-width: 1450px) {
    .btns--container {
      flex-direction: column;
      max-width: 125px;
      box-shadow: 1px 1px 3px 2px #0000003b;
    }
    .dropdown--container {
      left: 117px;
      top: 0;
    }
  }
`);

function MonkeyConfig() {
    var cfg = this,
        /* Data object passed to the constructor */
        data,
        /* Configuration parameters (data.parameters or data.params) */
        params,
        /* Current values of configuration parameters */
        values = {},
        /* Identifier used to store/retrieve configuration */
        storageKey,
        /* Is the configuration dialog displayed? */
        displayed,
        /* Currently displayed window/layer */
        openWin, openLayer,
        /* DOM element wrapping the configuration form */
        container,
        /* Darkened overlay used in the layer display mode */
        overlay;
    
    /**
     * Initialize configuration 
     * 
     * @param newData New data object
     */
    function init(newData) {
        data = newData;

        if (data) {
            params = data.parameters || data.params;
            
            if (data.buttons === undefined)
                /* Set default buttons */
                data.buttons = [ 'save', 'defaults', 'cancel' ];
            
            if (data.title === undefined)
                /*
                 * If GM_getMetadata is available, get the name of the script
                 * and use it in the dialog title
                 */
                if (typeof GM_getMetadata == 'function') {
                    var scriptName = GM_getMetadata('name');
                    data.title = scriptName + ' Configuration'; 
                }
                else
                    data.title = 'Configuration';
        }
        
        /* Make a safe version of title to be used as stored value identifier */ 
        var safeTitle = data && data.title ?
                data.title.replace(/[^a-zA-Z0-9]/g, '_') : '';

        storageKey = '_MonkeyConfig_' + safeTitle + '_cfg';
        
        var storedValues;
        
        /* Load stored values (if present) */
        if (GM_getValue(storageKey))
            storedValues = JSON.parse(GM_getValue(storageKey));

        for (var name in params) {
            /* If there's a value defined in the passed data object, use it */
            if (params[name]['value'] !== undefined)
                set(name, params[name].value);
            /* Check if there's a stored value for this parameter */
            else if (storedValues && storedValues[name] !== undefined)
                set(name, storedValues[name]);            
            /* Otherwise, set the default value (if defined) */
            else if (params[name]['default'] !== undefined)
                set(name, params[name]['default']);
            else
                set(name, '');
        }

        if (data.menuCommand) {
            /* Add an item to the User Script Commands menu */
            var caption = data.menuCommand !== true ? data.menuCommand :
                data.title;
            
            GM_registerMenuCommand(caption, function () { cfg.open(); });
        }

        /* Expose public methods */
        cfg.open = open;
        cfg.close = close;
        cfg.get = get;
        cfg.set = function (name, value) {
            set(name, value);
            update();
        };
    }
    
    /**
     * Get the value of a configuration parameter
     * 
     * @param name Name of the configuration parameter
     * @returns Value of the configuration parameter
     */
    function get(name) {
        return values[name];
    }
    
    /**
     * Set the value of a configuration parameter
     * 
     * @param name Name of the configuration parameter
     * @param value New value of the configuration parameter
     */
    function set(name, value) {
        values[name] = value;
    }
    
    /**
     * Reset configuration parameters to default values
     */ 
    function setDefaults() {
        for (var name in params) {
            if (typeof params[name]['default'] !== 'undefined') {
                set(name, params[name]['default']);
            }
        }
    }
    
    /**
     * Render the configuration dialog
     */
    function render() {
        var html = '<div class="__MonkeyConfig_container">' +
            '<h1>' + data.title + '</h1>' +
            '<table>';

        for (var name in params) {
            html += MonkeyConfig.formatters['tr'](name, params[name]);}

        html += '<tr><td colspan="2" class="__MonkeyConfig_buttons">' +
                '<table><tr>';

        /* Render buttons */
        for (var button in data.buttons) {
            html += '<td>';
            
            switch (data.buttons[button]) {
            case 'cancel':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_cancel">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.cancel + '" />&nbsp;' +
                    'Cancel</button>';
                break;
            case 'defaults':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_defaults">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.arrow_undo + '" />&nbsp;' +
                    'Set&nbsp;Defaults</button>';
                break;
            case 'save':
                html += '<button type="button" ' +
                    'id="__MonkeyConfig_button_save">' +
                    '<img src="data:image/png;base64,' +
                        MonkeyConfig.res.icons.tick + '" />&nbsp;' +
                    'Save</button>';
                break;
            }
            
            html += '</td>';
        }
        
        html += '</tr></table></td></tr>';
        
        html += "</table><div>";

        return html;
    }
    
    /**
     * Update the fields in the dialog to reflect current values 
     */
    function update() {
        /* Do nothing if the dialog is not currently displayed */
        if (!displayed)
            return;
        
        for (var name in params) {
            var value = values[name];
            
            switch (params[name].type) {
            case 'checkbox':
                var elem = container.querySelector('[name="' + name + '"]');
                elem.checked = !!value;
                break;
            case 'custom':
                params[name].set(value, container
                        .querySelector('#__MonkeyConfig_parent_' + name));
                break;
            case 'number': case 'text':
                var elem = container.querySelector('[name="' + name + '"]');
                elem.value = value;
                break;
            case 'select':
                var elem = container.querySelector('[name="' + name + '"]');
                
                if (elem.tagName.toLowerCase() == 'input') {
                    if (elem.type && elem.type == 'radio') {
                        /* Single selection with radio buttons */
                        elem = container.querySelector(
                            '[name="' + name + '"][value="' + value + '"]');
                        elem.checked = true;
                    }
                    else if (elem.type && elem.type == 'checkbox') {
                        /* Multiple selection with checkboxes */
                        var checkboxes = container.querySelectorAll(
                            'input[name="' + name + '"]');

                        for (var i = 0; i < checkboxes.length; i++)
                            checkboxes[i].checked = 
                                (value.indexOf(checkboxes[i].value) > -1);
                    }
                }
                else if (elem.tagName.toLowerCase() == 'select')
                    if (elem.multiple) {
                        /* Multiple selection element */
                        var options = container.querySelectorAll(
                            'select[name="' + name + '"] option');
                            
                        for (var i = 0; i < options.length; i++)
                            options[i].selected =
                                (value.indexOf(options[i].value) > -1);
                    }
                    else
                        /* Single selection element */
                        elem.value = value;
                break;
            }
        }
    }
    
    /**
     * Save button click event handler
     */
    function saveClick() {
        for (name in params) {
            switch (params[name].type) {
            case 'checkbox':
                var elem = container.querySelector('[name="' + name + '"]');
                values[name] = elem.checked;
                break;
            case 'custom':
                values[name] = params[name].get(container
                        .querySelector('#__MonkeyConfig_parent_' + name));
                break;
            case 'number': case 'text':
                var elem = container.querySelector('[name="' + name + '"]');
                values[name] = elem.value;
                break;
            case 'select':
                var elem = container.querySelector('[name="' + name + '"]');

                if (elem.tagName.toLowerCase() == 'input') {
                    if (elem.type && elem.type == 'radio')
                        /* Single selection with radio buttons */
                        values[name] = container.querySelector(
                            '[name="' + name + '"]:checked').value;
                    else if (elem.type && elem.type == 'checkbox') {
                        /* Multiple selection with checkboxes */
                        values[name] = [];
                        var inputs = container.querySelectorAll(
                            'input[name="' + name + '"]');

                        for (var i = 0; i < inputs.length; i++)
                            if (inputs[i].checked)
                                values[name].push(inputs[i].value);
                    }
                }
                else if (elem.tagName.toLowerCase() == 'select' && elem.multiple) {
                    /* Multiple selection element */
                    values[name] = [];
                    var options = container.querySelectorAll(
                        'select[name="' + name + '"] option');

                    for (var i = 0; i < options.length; i++)
                        if (options[i].selected)
                            values[name].push(options[i].value);
                }
                else
                    values[name] = elem.value;
                break;
            }
        }
        
        GM_setValue(storageKey, JSON.stringify(values));
        
        close();
        
        if (data.onSave)
            data.onSave(values);
    }
    
    /**
     * Cancel button click event handler 
     */
    function cancelClick() {
        close();
    }
    
    /**
     * Set Defaults button click event handler
     */
    function defaultsClick() {
        setDefaults();
        update();
    }

    /**
     * Open configuration dialog
     * 
     * @param mode
     *            Display mode ("iframe", "layer", or "window", defaults to
     *            "iframe")
     * @param options
     *            Display mode options
     */
    function open(mode, options) {
        function openDone() {
            /* Attach button event handlers */
            var button;
            
            if (button = container.querySelector('#__MonkeyConfig_button_save'))
                button.addEventListener('click', saveClick, true);
            if (button = container.querySelector('#__MonkeyConfig_button_cancel'))
                button.addEventListener('click', cancelClick, true);
            if (button = container.querySelector('#__MonkeyConfig_button_defaults'))
                button.addEventListener('click', defaultsClick, true);
            
            displayed = true;
            update();
        }
        
        switch (mode) {
        case 'window':        
            var windowFeatures = {
                location: 'no',
                status: 'no',
                left: window.screenX,
                top: window.screenY,
                width: 100,
                height: 100
            };
            
            /* Additional features may be specified as an option */
            if (options && options.windowFeatures)
                for (var name in options.windowFeatures)
                    windowFeatures[name] = options.windowFeatures[name];

            var featuresArray = [];
            
            for (var name in windowFeatures)
                featuresArray.push(name + '=' + windowFeatures[name]);

            var win = window.open('', data.title, featuresArray.join(','));
            
            /* Find head and body (then call the blood spatter analyst) */
            var head = win.document.getElementsByTagName('head')[0],
                body = win.document.getElementsByTagName('body')[0];

            head.innerHTML = '<title>' + data.title + '</title>' + 
                '<style type="text/css">' +
                MonkeyConfig.res.stylesheets.main + '</style>';
            
            body.className = '__MonkeyConfig_window';
            /* Place the rendered configuration dialog inside the window body */
            body.innerHTML = render();

            /* Find the container (CBAN-3489) */
            container = win.document.querySelector('.__MonkeyConfig_container');
            
            /* Resize window to the dimensions of the container div */
            win.innerWidth = container.clientWidth;
            win.resizeBy(0, -win.innerHeight + container.clientHeight);
            
            /* Place the window centered relative to the parent */
            win.moveBy(Math.round((window.outerWidth - win.outerWidth) / 2),
                Math.round((window.outerHeight - win.outerHeight) / 2));
            
            openWin = win;
            
            openDone();
            
            break;
        case 'layer':
            if (!MonkeyConfig.styleAdded) {
                GM_addStyle(MonkeyConfig.res.stylesheets.main);
                MonkeyConfig.styleAdded = true;
            }
            
            var body = document.querySelector('body');
            
            /* Create the layer element */
            openLayer = document.createElement('div');
            openLayer.className = '__MonkeyConfig_layer';
            
            /* Create the overlay */
            overlay = document.createElement('div');
            overlay.className = '__MonkeyConfig_overlay';
            overlay.style.left = 0;
            overlay.style.top = 0;
            overlay.style.width = window.innerWidth + 'px';
            overlay.style.height = window.innerHeight + 'px';
            overlay.style.zIndex = 9999;
            
            body.appendChild(overlay);         
            body.appendChild(openLayer);
            
            /* 
             * Place the rendered configuration dialog inside the layer element
             */
            openLayer.innerHTML = render();
            
            /* Position the layer in the center of the viewport */
            openLayer.style.left = Math.round((window.innerWidth -
                    openLayer.clientWidth) / 2) + 'px';
            openLayer.style.top = Math.round((window.innerHeight -
                    openLayer.clientHeight) / 2) + 'px';
            openLayer.style.zIndex = 9999;
            
            container = document.querySelector('.__MonkeyConfig_container');
            
            openDone();
            
            break;
        case 'iframe':
        default:
            if (!MonkeyConfig.styleAdded) {
                GM_addStyle(MonkeyConfig.res.stylesheets.main);
                MonkeyConfig.styleAdded = true;
            }
        
            var body = document.querySelector('body');
            var iframe = document.createElement('iframe');
            
            /* Create the layer element */
            openLayer = document.createElement('div');
            openLayer.className = '__MonkeyConfig_layer';
            
            /* Create the overlay */
            overlay = document.createElement('div');
            overlay.className = '__MonkeyConfig_overlay';
            overlay.style.left = 0;
            overlay.style.top = 0;
            overlay.style.width = window.innerWidth + 'px';
            overlay.style.height = window.innerHeight + 'px';
            overlay.style.zIndex = 9999;
            
            iframe.id = '__MonkeyConfig_frame';
            /* 
             * Make the iframe transparent so that it remains invisible until
             * the document inside it is ready
             */
            iframe.style.opacity = 0;
            iframe.src = 'about:blank';
            
            /* Make the iframe seamless with no border and no scrollbars */ 
            if (undefined !== iframe.frameborder)
                iframe.frameBorder = '0';
            if (undefined !== iframe.scrolling)
                iframe.scrolling = 'no';
            if (undefined !== iframe.seamless)
                iframe.seamless = true;
            
            /* Do the rest in the load event handler */
            iframe.addEventListener('load', function () {
                iframe.contentDocument.body.innerHTML = render();
                iframe.style.opacity = 1;
                
                /* Append the style to the head */
                var head = iframe.contentDocument.querySelector('head'),
                    style = iframe.contentDocument.createElement('style');
                style.setAttribute('type', 'text/css');
                style.appendChild(iframe.contentDocument.createTextNode(
                        MonkeyConfig.res.stylesheets.main));
                head.appendChild(style);
                
                var body = iframe.contentDocument.querySelector('body');
                body.className = '__MonkeyConfig_body';
                
                container = iframe.contentDocument
                    .querySelector('.__MonkeyConfig_container');

                iframe.width = container.clientWidth;
                iframe.height = container.clientHeight;

                /* Position the layer in the center of the viewport */
                openLayer.style.left = Math.round((window.innerWidth -
                        openLayer.clientWidth) / 2) + 'px';
                openLayer.style.top = Math.round((window.innerHeight -
                        openLayer.clientHeight) / 2) + 'px';
                openLayer.style.zIndex = 9999;
                
                openDone();
            }, false);

            setTimeout(function () {
                iframe.width = container.clientWidth;
                iframe.height = container.clientHeight;
                
                /* Position the layer in the center of the viewport */
                openLayer.style.left = Math.round((window.innerWidth -
                        openLayer.clientWidth) / 2) + 'px';
                openLayer.style.top = Math.round((window.innerHeight -
                        openLayer.clientHeight) / 2) + 'px';
                openLayer.style.zIndex = 9999;
            }, 0);
            
            body.appendChild(overlay);         
            body.appendChild(openLayer);
            openLayer.appendChild(iframe);
        
            break;
        }
    }
    
    /**
     * Close configuration dialog 
     */
    function close() {
        if (openWin) {
            openWin.close();
            openWin = undefined;
        }
        else if (openLayer) {
            openLayer.parentNode.removeChild(openLayer);
            openLayer = undefined;
            
            if (overlay) {
                overlay.parentNode.removeChild(overlay);
                overlay = undefined;
            }
        }
        
        displayed = false;
    }

    init(arguments[0]);
}

/**
 * Replace double quotes with entities so that the string can be safely used
 * in a HTML attribute
 * 
 * @param string A string
 * @returns String with double quotes replaced with entities
 */
MonkeyConfig.esc = function (string) {
    return string.replace(/"/g, '&quot;');
};

MonkeyConfig.HTML = {
    '_field': function (name, options, data) {
        var html;
        
        if (options.type && MonkeyConfig.HTML[options.type])
            html = MonkeyConfig.HTML[options.type](name, options, data);
        else
            return;
        
        if (/\[FIELD\]/.test(options.html)) {
            html = options.html.replace(/\[FIELD\]/, html);
        }
        
        return html;
    },
    '_label': function (name, options, data) {
        var label = options['label'] ||
            name.substring(0, 1).toUpperCase() + name.substring(1)
                .replace(/_/g, '&nbsp;');

        return '<label for="__MonkeyConfig_field_' + name + '">' + label +
            '</label>';
    },
    'checkbox': function (name, options, data) {
        return '<input id="__MonkeyConfig_field_' + name + 
            '" type="checkbox" name="' + name + '" />'; 
    },
    'custom': function (name, options, data) {
        return options.html;
    },
    'number': function (name, options, data) {
        return '<input id="__MonkeyConfig_field_' + name + '" ' +
            'type="text" class="__MonkeyConfig_field_number" ' +
            'name="' + name + '" />';
    },
    'select': function (name, options, data) {
        var choices = {}, html = '';
        
        if (options.choices.constructor == Array) {
            /* options.choices is an array -- build key/value pairs */
            for (var i = 0; i < options.choices.length; i++)
                choices[options.choices[i]] = options.choices[i];
        }
        else
            /* options.choices is an object -- use it as it is */
            choices = options.choices;

        if (!options.multiple) {
            /* Single selection */
            if (!/^radio/.test(options.variant)) {
                /* Select element */
                html += '<select id="__MonkeyConfig_field_' + name + '" ' +
                    'class="__MonkeyConfig_field_select" ' +
                    'name="' + name + '">';
            
                for (var value in choices)
                    html += '<option value="' + MonkeyConfig.esc(value) + '">' +
                        choices[value] + '</option>';
                
                html += '</select>';
            }
            else {
                /* Radio buttons */
                for (var value in choices) {
                    html += '<label><input type="radio" name="' + name + '" ' +
                        'value="' + MonkeyConfig.esc(value) + '" />&nbsp;' +
                        choices[value] + '</label>' +
                        (/ column/.test(options.variant) ? '<br />' : '');
                }
            }
        }
        else {
            /* Multiple selection */
            if (!/^checkbox/.test(options.variant)) {
                /* Checkboxes */
                html += '<select id="__MonkeyConfig_field_' + name + '" ' +
                    'class="__MonkeyConfig_field_select" ' +
                    'multiple="multiple" ' +
                    'name="' + name + '">';
                
                for (var value in choices)
                    html += '<option value="' + MonkeyConfig.esc(value) + '">' +
                        choices[value] + '</option>';
                
                html += '</select>';
            }
            else {
                /* Select element */
                for (var value in choices) {
                    html += '<label><input type="checkbox" ' +
                        'name="' + name + '" ' +
                        'value="' + MonkeyConfig.esc(value) + '" />&nbsp;' +
                        choices[value] + '</label>' +
                        (/ column/.test(options.variant) ? '<br />' : '');
                }
            }
        }
        
        return html;
    },
    'text': function (name, options, data) {
        if (options.long)
            return '<textarea id="__MonkeyConfig_field_' + name + '" ' +
                'class="__MonkeyConfig_field_text" ' +
                (!isNaN(options.long) ? 'rows="' + options.long + '" ' : '') +
                'name="' + name + '"></textarea>';
        else
            return '<input id="__MonkeyConfig_field_' + name + '" ' +
                'type="text" class="__MonkeyConfig_field_text" ' +
                'name="' + name + '" />';
    }
};

MonkeyConfig.formatters = {
    'tr': function (name, options, data) {
        var html = '<tr>';

        switch (options.type) {
        case 'checkbox':
            /* Checkboxes get special treatment */
            html += '<td id="__MonkeyConfig_parent_' + name + '" colspan="2">';
            html += MonkeyConfig.HTML['_field'](name, options, data) + ' ';
            html += MonkeyConfig.HTML['_label'](name, options, data);
            html += '</td>';
            break;
        default:
            html += '<td>';
            html += MonkeyConfig.HTML['_label'](name, options, data);
            html += '</td><td id="__MonkeyConfig_parent_' + name + '">';
            html += MonkeyConfig.HTML['_field'](name, options, data);
            html += '</td>';
            break;
        }
        
        html += '</tr>';

        return html;
    }
};

/* Has the stylesheet been added? */
MonkeyConfig.styleAdded = false;

/* Resources */
MonkeyConfig.res = {};

/* Icons */
MonkeyConfig.res.icons = {
    'arrow_undo': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIJSURBVDjLpVM9aJNRFD35GsRSoUKKzQ/B\
0NJJF3EQlKrVgijSCBmC4NBFKihIcXBwEZdSHVoUwUInFUEkQ1DQ4CKiFsQsTrb5xNpgaZHw2Uog\
5t5zn0NJNFaw0guX97hwzuPcc17IOYfNlIdNVrhxufR6xJkZjAbSQGXjNAorqixSWFDV3KPhJ+UG\
LtSQMPryrDscPwLnAHOEOQc6gkbUpIagGmApWIb/pZRX4fjj889nWiSQtgYyBZ1BTUEj6AjPa0P7\
1nb0Jfqwa+futIheHrzRn2yRQCUK/lOQhApBJVQJChHfnkCqOwWEQ+iORJHckUyX5ksvAEyGNuJC\
+s6xCRXNHNxzKMmQ4luwgjfvZp69uvr2+IZcyJ8rjIporrxURggetnV0QET3rrPxzMNM2+n7p678\
jUTrCiWhphAjVHR9DlR0WkSzf4IHxg5MSF0zXZEuVKWKSlCBCostS8zeG7oV64wPqxInbw86lbVX\
KEQ8mkAqmUJ4SxieeVhcnANFC02C7N2h69HO2IXeWC8MDj2JnqaFNAMd8f3HKjx6+LxQRmnOz1OZ\
axKIaF1VISYwB9ARZoQaYY6o1WpYCVYxt+zDn/XzVBv/MOWXW5J44ubRyVgkelFpmF/4BJVfOVDl\
VyqLVBZI5manPjajDOdcswfG9k/3X9v3/vfZv7rFBanriIo++J/f+BMT+YWS6hXl7QAAAABJRU5E\
rkJggg==',
    'cancel': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHdSURBVDjLpZNraxpBFIb3a0ggISmmNISW\
XmOboKihxpgUNGWNSpvaS6RpKL3Ry//Mh1wgf6PElaCyzq67O09nVjdVlJbSDy8Lw77PmfecMwZg\
/I/GDw3DCo8HCkZl/RlgGA0e3Yfv7+DbAfLrW+SXOvLTG+SHV/gPbuMZRnsyIDL/OASziMxkkKkU\
QTJJsLaGn8/iHz6nd+8mQv87Ahg2H9Th/BxZqxEkEgSrq/iVCvLsDK9awtvfxb2zjD2ARID+lVVl\
babTgWYTv1rFL5fBUtHbbeTJCb3EQ3ovCnRC6xAgzJtOE+ztheYIEkqbFaS3vY2zuIj77AmtYYDu\
sPy8/zuvunJkDKXM7tYWTiyGWFjAqeQnAD6+7ueNx/FLpRGAru7mcoj5ebqzszil7DggeF/DX1nB\
N82rzPqrzbRayIsLhJqMPT2N83Sdy2GApwFqRN7jFPL0tF+10cDd3MTZ2AjNUkGCoyO6y9cRxfQo\
wFUbpufr1ct4ZoHg+Dg067zduTmEbq4yi/UkYidDe+kaTcP4ObJIajksPd/eyx3c+N2rvPbMDPbU\
FPZSLKzcGjKPrbJaDsu+dQO3msfZzeGY2TCvKGYQhdSYeeJjUt21dIcjXQ7U7Kv599f4j/oF55W4\
g/2e3b8AAAAASUVORK5CYII=',
    'tick': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0\
U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLvZPZLkNhFIV75zjvYm7VGFNC\
qoZUJ+roKUUpjRuqp61Wq0NKDMelGGqOxBSUIBKXWtWGZxAvobr8lWjChRgSF//dv9be+9trCwAI\
/vIE/26gXmviW5bqnb8yUK028qZjPfoPWEj4Ku5HBspgAz941IXZeze8N1bottSo8BTZviVWrEh5\
46EO03EXpuJOdG63otJbjBKHkEp/Ml6yNYYzpuezWL4s5VMtT8acCMQcb5XL3eJE8VgBlR7BeMGW\
9Z4yT9y1CeyucuhdTGDxfftaBO7G4L+zg91UocxVmCiy51NpiP3n2treUPujL8xhOjYOzZYsQWAN\
yRYlU4Y9Br6oHd5bDh0bCpSOixJiWx71YY09J5pM/WEbzFcDmHvwwBu2wnikg+lEj4mwBe5bC5h1\
OUqcwpdC60dxegRmR06TyjCF9G9z+qM2uCJmuMJmaNZaUrCSIi6X+jJIBBYtW5Cge7cd7sgoHDfD\
aAvKQGAlRZYc6ltJlMxX03UzlaRlBdQrzSCwksLRbOpHUSb7pcsnxCCwngvM2Rm/ugUCi84fycr4\
l2t8Bb6iqTxSCgNIAAAAAElFTkSuQmCC'
};

/* Stylesheets */
MonkeyConfig.res.stylesheets = {
    'main': '\
body.__MonkeyConfig_window {\
    appearance: window !important;\
    -moz-appearance: window !important;\
    background: auto;\
    font-family: sans-serif !important;\
    height: 100% !important;\
    margin: 0 !important;\
    padding: 0 !important;\
    width: 100% !important;\
}\
\
div.__MonkeyConfig_container {\
    display: table !important;\
    font-family: sans-serif !important;\
    padding: 0.3em !important;\
}\
\
body.__MonkeyConfig_window div.__MonkeyConfig_container {\
    appearance: window !important;\
    -moz-appearance: window !important;\
    height: 100%;\
    width: 100%;\
}\
\
div.__MonkeyConfig_container h1 {\
    border-bottom: solid 1px #999 !important;\
    font-family: sans-serif !important;\
    font-size: 120% !important;\
    margin: 0 !important;\
    padding: 0 0 0.3em 0 !important;\
}\
\
div.__MonkeyConfig_container table {\
    border-spacing: 0 !important;\
    margin: 0 !important;\
}\
\
div.__MonkeyConfig_container table td {\
    border: none !important;\
    line-height: 100% !important;\
    padding: 0.3em !important;\
    text-align: left !important;\
    vertical-align: top !important;\
    white-space: nowrap !important;\
}\
\
div.__MonkeyConfig_container table td.__MonkeyConfig_buttons {\
    padding: 0.2em 0 !important;\
}\
\
.__MonkeyConfig_field_number {\
    width: 5em !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons table {\
    border-top: solid 1px #999 !important;\
    width: 100% !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons td {\
    padding: 0.6em 0.3em 0.1em 0.3em !important;\
    text-align: center !important;\
    vertical-align: top;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons button {\
    appearance: button !important;\
    -moz-appearance: button !important;\
    background-position: 8px 50% !important;\
    background-repeat: no-repeat !important;\
    padding: 3px 8px 3px 24px !important;\
    padding: 3px 8px !important;\
    white-space: nowrap !important;\
}\
\
div.__MonkeyConfig_container td.__MonkeyConfig_buttons button img {\
    vertical-align: middle !important;\
}\
\
div.__MonkeyConfig_layer {\
    display: table !important;\
    position: fixed !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container,\
body.__MonkeyConfig_body > div.__MonkeyConfig_container {\
    background: #eee linear-gradient(180deg,\
        #f8f8f8 0, #ddd 100%) !important;\
    border-radius: 0.5em !important;\
    box-shadow: 2px 2px 16px #000 !important;\
    color: #000 !important;\
    font-family: sans-serif !important;\
    font-size: 11pt !important;\
    padding: 1em 1em 0.4em 1em !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container label,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container input,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container select,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container textarea,\
div.__MonkeyConfig_layer div.__MonkeyConfig_container button {\
    color: #000 !important;\
    font-family: sans-serif !important;\
    font-size: 11pt !important;\
    line-height: 100% !important;\
    margin: 0 !important;\
    vertical-align: baseline !important;\
}\
\
div.__MonkeyConfig_container label {\
    line-height: 120% !important;\
    vertical-align: baseline !important;\
}\
\
div.__MonkeyConfig_container textarea {\
    vertical-align: text-top !important;\
    width: 100%;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container input[type="text"] {\
    appearance: textfield !important;\
    -moz-appearance: textfield !important;\
    background: #fff !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container h1 {\
    font-weight: bold !important;\
    text-align: left !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td.__MonkeyConfig_buttons button,\
body > div.__MonkeyConfig_container td.__MonkeyConfig_buttons button {\
    appearance: button !important;\
    -moz-appearance: button !important;\
    background: #ccc linear-gradient(180deg,\
        #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important;\
    border-style: solid !important;\
    border-width: 1px !important;\
    border-radius: 0.5em !important;\
    box-shadow: 0 0 1px #000 !important;\
    color: #000 !important;\
    font-size: 11pt !important;\
}\
\
div.__MonkeyConfig_layer div.__MonkeyConfig_container td.__MonkeyConfig_buttons button:hover,\
body > div.__MonkeyConfig_container td.__MonkeyConfig_buttons button:hover {\
    background: #d2d2d2 linear-gradient(180deg,\
        #e2e2e2 0, #d2d2d2 45%, #c2c2c2 50%, #b2b2b2 100%) !important;\
}\
\
div.__MonkeyConfig_overlay {\
    background-color: #000 !important;\
    opacity: 0.6 !important;\
    position: fixed !important;\
}\
\
iframe#__MonkeyConfig_frame {\
    border: none !important;\
    box-shadow: 2px 2px 16px #000 !important;\
}\
\
body.__MonkeyConfig_body {\
    margin: 0 !important;\
    padding: 0 !important;\
}\
'
};

(() => {
  "use strict"
  
  // SETTINGS
  const cfg = new MonkeyConfig({
    title: "Better Google Search Settings",
    menuCommand: true,
    params: {
      Bypass_CSP: {
        type: "checkbox",
        default: false,
      },
    }
  })
  
  const BYPASS_CSP = cfg.get("Bypass_CSP") || false
  
  // HELPERS
  const qSelect = (selector) => document.querySelector(selector)
  const qSelectAll = (selectors) => document.querySelectorAll(selectors)
  const qAllCallback = (selector, callback) => Array.from(qSelectAll(selector)).forEach((el, i) => callback(el, i))  
  
  let relativeParent = qSelect("input").parentElement.parentElement.parentElement.parentElement
  
  // check for theme
  const getDefaultTheme = () => {
    if (window?.matchMedia("(prefers-color-scheme: dark)").matches) return true
    return false
  }
  const isDark = getDefaultTheme()
  
  // bypass content-security-policy (CSP) to allow the script to work on google images
  // WARNING: this makes innerHTML vulnerable to injection
  if (window.trustedTypes?.createPolicy && BYPASS_CSP) {
    relativeParent = qSelect("input").parentElement.parentElement.parentElement
    window.trustedTypes.createPolicy("default", {
      createHTML: (string, sink) => string
    });
  }
  
  const FILE_TYPE = "FILE_TYPE"
  const EXCLUDE = "EXCLUDE"
  const SITE = "SITE"
  const EXACT_QUERY = "EXACT_QUERY"
  const TERM_APPEARS = "TERM_APPEARS"
  const BEFORE = "BEFORE"
  const AFTER = "AFTER"
  const DOMAIN = "DOMAIN"
  const SETTINGS = "SETTINGS"
  const LANGUAGE = "LANGUAGE"
  
  // contains all possible search actions you can perform
    const fileTypeData = {
      name: "File Type", action: FILE_TYPE, data: "filetype:", isUnique: true, 
      choices: [
        {name: "PDF", data: "pdf"}, 
        {name: "DOC", data: "doc"}, 
        {name: "DOCX", data: "docx"}, 
        {name: "TXT", data: "txt"}, 
        {name: "LOG", data: "log"}, 
        {name: "PPT", data: "ppt"}, 
        {name: "PPTX", data: "pptx"}, 
        {name: "XML", data: "xml"}, 
        {name: "TORRENT", data: "torrent"},
        {name: "RTF", data: "rtf"}
      ]
    }
    const excludeData = {
      name: "Exclude", action: EXCLUDE, data: "-", isUnique: false, urlBased: false,
    }
    const siteData = {
      name: "Site", action: SITE, data: "site:", isUnique: true, urlBased: false,
      choices: [
        {name: "reddit", data: "reddit.com"}, 
        {name: "stack overflow", data: "stackoverflow.com"}, 
        {name: "youtube", data: "youtube.com"}, 
        {name: "twitter", data: "twitter.com"}, 
        {name: "facebook", data: "facebook.com"}, 
        {name: "custom", data: ""}
      ]
    }
    const domainData = {
      name: "Domain", action: DOMAIN, data: "site:", isUnique: true, urlBased: false,
      choices: [
        {name: ".com", data: ".com"}, 
        {name: ".org", data: ".org"}, 
        {name: ".edu", data: ".edu"}, 
        {name: ".net", data: ".net"}
      ]
    }
    const exactQueryData = {
      name: "Exact Query", action: EXACT_QUERY, data: `""`, isUnique: false, urlBased: false,
    }
    const termAppearsData = {
      name: "Term Appears", action: TERM_APPEARS, isUnique: false, urlBased: false,
      choices: [
        {name: "in the title of the page", data: "allintitle:"}, 
        {name: "in the text of the page", data: "allintext:"}, 
        {name: "in the URL of the page", data: "allinurl:"}, 
        {name: "in links to the page", data: "allinanchor:"}
      ]
    }
    const beforeData = {
      name: "Before (Time)", action: BEFORE, data: "before:", isUnique: true, urlBased: false,
    }
    const afterData = {
      name: "After (Time)", action: AFTER, data: "after:", isUnique: true, urlBased: false,
    }
    const settingsData = {
      name: "Settings", action: SETTINGS, isUnique: true, urlBased: false,
    }
    const languageData = {
      name: "Language", action: LANGUAGE, data: "&lr=lang_", isUnique: true, urlBased: true,
      choices: [
        {name: "Any", data: ""},
        {name: "Afrikaans", data: "af"},
        {name: "Arabic", data: "ar"},
        {name: "Armenian", data: "hy"},
        {name: "Byelorussian", data: "be"},
        {name: "Bulgarian", data: "bg"},
        {name: "Catalan", data: "ca"},
        {name: "Chinese (Simplified)", data: "zh-CN"},
        {name: "Chinese (Traditional)", data: "zh-TW"},
        {name: "Croatian", data: "hr"},
        {name: "Czech", data: "cs"},
        {name: "Dutch", data: "nl"},
        {name: "Esperanto", data: "eo"},
        {name: "Estonian", data: "et"},
        {name: "English", data: "en"},
        {name: "Tagalog", data: "tl"},
        {name: "Finnish", data: "fi"},
        {name: "French", data: "fr"},
        {name: "German", data: "de"},
        {name: "Greek", data: "el"},
        {name: "Hebrew", data: "iw"},
        {name: "Hindi", data: "hi"},
        {name: "Hungarian", data: "hu"},
        {name: "Icelandic", data: "is"},
        {name: "Indonesian", data: "id"},
        {name: "Italian", data: "it"},
        {name: "Japanese", data: "ja"},
        {name: "Korean", data: "ko"},
        {name: "Latvian", data: "lv"},
        {name: "Lithuanian", data: "lt"},
        {name: "Norwegian", data: "no"},
        {name: "Persian", data: "fa"},
        {name: "Polish", data: "pl"},
        {name: "Romanian", data: "ro"},
        {name: "Russian", data: "ru"},
        {name: "Serbian", data: "sr"},
        {name: "Slovak", data: "sk"},
        {name: "Slovenian", data: "sl"},
        {name: "Spanish", data: "es"},
        {name: "Swahili", data: "sw"},
        {name: "Swedish", data: "sv"},
        {name: "Thai", data: "th"},
        {name: "Turkish", data: "tr"},
        {name: "Ukrainian", data: "uk"},
        {name: "Vietnamese", data: "vi"},
      ]
    }

    const actions = [
      excludeData, 
      exactQueryData, 
      beforeData, 
      afterData, 
      settingsData,
      fileTypeData, 
      siteData, 
      domainData, 
      termAppearsData, 
      languageData
    ]
  
  const toggleDropdown = (evt) => {
    if (evt.target !== evt.currentTarget) {
      evt.currentTarget.lastElementChild.classList.toggle("show")
      // theme dependent styles
      if (isDark) {
        evt.currentTarget.classList.toggle("btn--active")
      }
      else {
        evt.currentTarget.classList.toggle("btn--active-light")
      }
    }
    
    qAllCallback(".show", (el, i) => {
      if (el.previousElementSibling?.innerText !== evt.currentTarget.firstElementChild?.innerText) {
        el.classList.remove("show")
        el.parentElement.classList.remove("btn--active")
      }
    })
  }
  
  const createButtons = () => {
    relativeParent.style.position = "relative"

    const buttonsContainer = document.createElement("section")
    relativeParent.appendChild(buttonsContainer)
    buttonsContainer.classList.add("btns--container")
    actions.forEach((action, i) => {
      let caretDropdown = ""
      let dropdownMenu = ""
      let hasDropdown = false
      
      if (action?.choices) {
        hasDropdown = true
        caretDropdown = `<span class="caret-dropdown"></span>`
        
        // let parentAction = action.data
        let dropdownItems = action.choices.map((item) => `<li class="dropdown--items" data-action="${item.data}" data-parent-action="${action.data ? action.data : ""}" data-parent-action-index="${i}">${item.name}</li>`).join("")
            
        dropdownMenu = `
          <ul class="hide dropdown--container">
            ${dropdownItems}
          </ul>
        `
      }
      
      let flexBreak = ""
      if (actions[i].action === SETTINGS) flexBreak = `<div class="flex-break"><div>`
      
      if (hasDropdown) {
        buttonsContainer.innerHTML += `
          <section class="btn--container">
            <div class="btn">${action.name}</div>
            ${dropdownMenu}
          </section>
        `
      }
      else {
        buttonsContainer.innerHTML += `
          <section class="btn--container">
            <div class="btn">${action.name}</div>
          </section>
          ${flexBreak}
        `
      }
    })
  }
  
  createButtons()
  
  // attach buttons with dropdown function
  qAllCallback(".btn--container", (btn) => btn.addEventListener("click", toggleDropdown))
  qAllCallback(".dropdown--container", (el) => {
    el.addEventListener("click", (evt) => evt.stopPropagation())
    // add caret to buttons with dropdown
    el.previousElementSibling.classList.add("btn--caret")
  })
  
  
  // close dropdown menus when clicked on somewhere other than the dropdown areas
  window.onclick = (evt) => {
    if (!evt.target.matches(".btn")) {
      qAllCallback(".dropdown--container", ({classList}) => classList.contains("show") && classList.remove("show"))
      qAllCallback(".btn--active", ({classList}) => classList.remove("btn--active"))
    }
  }
  
  const attachActions = () => {
    // attach dropdown items with click handler
    qAllCallback(".dropdown--items", (item) => {
      item.addEventListener("click", (evt) => {
        const parentAction = evt.currentTarget.getAttribute("data-parent-action")
        const action = evt.currentTarget.getAttribute("data-action")
        const actionObject = actions[evt.currentTarget.getAttribute("data-parent-action-index")]
        const isUnique = actionObject.isUnique
        const actionType = actionObject.action
        const urlBased = actionObject.urlBased
        
        // special case for options that modify the search url and not the search bar
        if (urlBased) {
          let currentURL = window.location.href
          const expression = new RegExp("(&lr=lang_[a-z]{2}-[A-Z]{2}|&lr=lang_[a-z]{2}|&lr=lang_|&lr=lang|&lr=|&lr)", "gi")
          if (action === "") {
            document.location.href = currentURL.replace(expression, "")
            return
          }
          else if (currentURL.match(expression), "gi") currentURL = currentURL.replace(expression, "")
          document.location.href = `${currentURL}${parentAction}${action}`
          return
        }
        
        const input = qSelect("input")
        
        // check if unused or unique query already exists in search
        let expression = new RegExp(`(${parentAction}\\w+\\.\\w+|${parentAction}\\w+|${parentAction}\\.\\w+|${parentAction}:\\.\\w+|${parentAction}:\\.|${parentAction}\\w+.\\w+|${parentAction})`, "g")
        if (parentAction && input.value.match(expression)) {
          input.value = input.value.replace(expression, (search) => `${parentAction}${action}`)

          // only focus cursor on end of `site:` if it's blank (custom)
          if (actionType === SITE && action === "") {
            const searchPos = input.value.search(/(site:)/)
            input.setSelectionRange(searchPos + parentAction.length, searchPos + parentAction.length)
          }
          
          input.focus()
          return
        }
        // remove excess spaces
        input.value = input.value.replace(/\s{2,}/g, (search) => " ")
        
        // apply changes to search bar value
        input.value += ` ${parentAction}${action} `
        
        // trim and remove excess spaces
        input.value = input.value.trim().replace(/\s{2,}/g, (search) => " ")
        
        // focus on search bar
        input.focus()
      })
    })
    
    // attach non-having dropdown items with click handler
    qAllCallback(".btn--container", (el, i) => {
      if(el.childElementCount === 1) {
        el.firstElementChild.addEventListener("click", (evt) => {
          const action = actions[i].data
          
          // unique case for settings
          if (actions[i].action === SETTINGS) {
            cfg.open()
            return
          }
          
          const input = qSelect("input")
          
          // check if query is unique (only one of it should exist)
          const queryExists = new RegExp(`${action}`)
          if(actions[i].isUnique && input.value.match(queryExists)){
            const searchPos = input.value.search(queryExists)
            input.setSelectionRange(searchPos + action.length, searchPos + action.length)
            input.focus()
            return
          }
          
          // check if unused query is already in search bar
          const expression = new RegExp(`(${action}\\W)|(\\W${action}\\B)`, "g")
          if(input.value.match(expression)){
            // set cursor at unused query
            const searchPos = input.value.search(expression)
            
            if(action.length > 2) input.setSelectionRange(searchPos + action.length + 1, searchPos + action.length + 1)
            else input.setSelectionRange(searchPos + 2, searchPos + 2)
            
          }
          else{
            input.value = input.value.trim()
            // apply changes to search bar value
            input.value += ` ${action} `
            input.value = input.value.trim()
          }
          
          if(action === `""`) {
            const quotationsPos = input.value.search(`""`)
            input.setSelectionRange(quotationsPos + 1, quotationsPos + 1)
          }
          //focus on search bar
          input.focus()
        })
      }
    })
  }
  
  attachActions()
  
  if(!isDark){
    qAllCallback(".btns--container", (el) => el.classList.add("btns--container-light"))
    qAllCallback(".btn--container", (el) => el.classList.add("btn--container-light"))
    qAllCallback(".dropdown--container", (el) => el.classList.add("dropdown--container-light"))
    qAllCallback(".dropdown--items", (el) => el.classList.add("dropdown--items-light"))
  }
  
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址