- // ==UserScript==
- // @name Instant Search Switcher
- // @namespace http://tampermonkey.net/
- // @version 1.5
- // @description Changes search engine from one site to another without deleting search query. Draggable UI.
- // @author Faisal Bhuiyan
- // @match *://*.bing.com/*search*
- // @match *://*.bing.com/chat*
- // @match https://www.google.com/search*
- // @match https://www.qwant.com/*
- // @match *://yandex.com/*search*
- // @match *://search.brave.com/*search*
- // @match *://duckduckgo.com/*
- // @license MIT
- // @grant GM_setValue
- // @grant GM_getValue
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- const searchEngines = [
- {
- name: 'Google',
- url: 'https://www.google.com/search',
- param: 'q'
- },
- {
- name: 'Bing',
- url: 'https://www.bing.com/search',
- param: 'q'
- },
- {
- name: 'Copilot',
- url: 'https://www.bing.com/chat',
- param: 'q',
- additionalParams: '&sendquery=1&FORM=SCCODX'
- },
- {
- name: 'Qwant',
- url: 'https://www.qwant.com/',
- param: 'q'
- },
- {
- name: 'Brave',
- url: 'https://search.brave.com/search',
- param: 'q'
- },
- {
- name: 'Yandex',
- url: 'https://yandex.com/search',
- param: 'text'
- },
- {
- name: 'Perplexity',
- url: 'https://www.perplexity.ai/search',
- param: 'q'
- },
- {
- name: 'Phind',
- url: 'https://www.phind.com/search',
- param: 'q'
- },
- {
- name: 'Morphic',
- url: 'https://morphic.sh/search',
- param: 'q'
- },
- {
- name: 'DuckDuckGo',
- url: 'https://duckduckgo.com/',
- param: 'q'
- }
- ];
-
- // Default position - define this as a constant
- const defaultPosition = { top: '20px', right: '5rem', left: 'auto' };
-
- // Try to get saved position or use default
- let position;
- try {
- const savedPosition = GM_getValue('switcherPosition');
- // Only use saved position if it exists and has been manually set
- position = (savedPosition && GM_getValue('positionModified')) ? savedPosition : defaultPosition;
- } catch (e) {
- // If GM_getValue is not available, use defaultPosition
- position = defaultPosition;
- }
-
- // Create container for draggable functionality
- const container = document.createElement('div');
- container.style.position = 'fixed';
- container.style.top = position.top;
- container.style.right = position.right;
- container.style.left = position.left;
- container.style.zIndex = '9999';
- container.style.cursor = 'move';
- container.style.userSelect = 'none';
- container.style.touchAction = 'none';
- container.style.display = 'flex';
- container.style.flexDirection = 'row';
- container.style.alignItems = 'center';
- container.style.padding = '4px';
- container.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
- container.style.borderRadius = '4px';
- container.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
-
- // Create a handle for dragging
- const dragHandle = document.createElement('div');
- dragHandle.innerHTML = '⋮⋮'; // vertical dots as drag indicator
- dragHandle.style.marginRight = '5px';
- dragHandle.style.cursor = 'move';
- dragHandle.style.color = '#555';
- dragHandle.style.fontSize = '16px';
- dragHandle.style.paddingRight = '5px';
- dragHandle.title = 'Drag to move';
-
- // Create reset button
- const resetButton = document.createElement('div');
- resetButton.innerHTML = '↺'; // reset icon
- resetButton.style.marginLeft = '5px';
- resetButton.style.cursor = 'pointer';
- resetButton.style.color = '#555';
- resetButton.style.fontSize = '16px';
- resetButton.style.padding = '0 5px';
- resetButton.title = 'Reset position';
- resetButton.style.display = 'flex';
- resetButton.style.alignItems = 'center';
- resetButton.style.justifyContent = 'center';
-
- // Create the floating select box
- const selectBox = document.createElement('select');
- selectBox.style.fontSize = '16px';
- selectBox.style.padding = '5px';
- selectBox.style.borderRadius = '4px';
- selectBox.style.backgroundColor = '#fff';
- selectBox.style.color = '#000'; // Force black text
- selectBox.style.border = '1px solid #ccc';
- selectBox.style.outline = 'none';
- selectBox.style.cursor = 'pointer';
-
- // Add CSS to ensure text remains visible in both light and dark themes
- const style = document.createElement('style');
- style.textContent = `
- .search-switcher-select {
- color: #000 !important;
- -webkit-text-fill-color: #000 !important;
- }
- .search-switcher-select option {
- background-color: #fff !important;
- color: #000 !important;
- -webkit-text-fill-color: #000 !important;
- }
- `;
- document.head.appendChild(style);
-
- selectBox.className = 'search-switcher-select';
-
- // Add an empty option as the first element
- const emptyOption = document.createElement('option');
- emptyOption.value = '';
- emptyOption.textContent = 'Select';
- emptyOption.disabled = true;
- emptyOption.selected = true;
- selectBox.appendChild(emptyOption);
-
- // Add search engines to the select box
- searchEngines.forEach(engine => {
- const option = document.createElement('option');
- option.value = engine.url;
- option.textContent = engine.name;
- selectBox.appendChild(option);
- });
-
- // Add elements to container
- container.appendChild(dragHandle);
- container.appendChild(selectBox);
- container.appendChild(resetButton);
-
- // Append the container to the body
- document.body.appendChild(container);
-
- // Reset button functionality
- resetButton.addEventListener('click', (e) => {
- e.stopPropagation();
-
- // Apply default position
- container.style.top = defaultPosition.top;
- container.style.right = defaultPosition.right;
- container.style.left = defaultPosition.left;
-
- // Save the default settings
- try {
- GM_setValue('switcherPosition', defaultPosition);
- GM_setValue('positionModified', false);
- } catch (e) {
- // Silently fail if GM_setValue is not available
- }
- });
-
- // Detect changes to the select box
- selectBox.addEventListener('change', () => {
- const selectedEngine = searchEngines[selectBox.selectedIndex - 1];
- // Try to get query parameter from either 'q' or 'text' depending on current search engine
- const currentQuery = new URLSearchParams(window.location.search).get('q') ||
- new URLSearchParams(window.location.search).get('text');
-
- if (currentQuery && selectedEngine) {
- const additionalParams = selectedEngine.additionalParams || '';
- window.location.href = `${selectedEngine.url}?${selectedEngine.param}=${encodeURIComponent(currentQuery)}${additionalParams}`;
- }
- });
-
- // Make the container draggable
- let isDragging = false;
- let dragOffsetX, dragOffsetY;
-
- // Function to start dragging
- function startDrag(clientX, clientY) {
- isDragging = true;
-
- // Calculate the offset of the mouse within the container
- const rect = container.getBoundingClientRect();
- dragOffsetX = clientX - rect.left;
- dragOffsetY = clientY - rect.top;
-
- // Prevent text selection during drag
- document.body.style.userSelect = 'none';
- }
-
- // Function to handle dragging
- function drag(clientX, clientY) {
- if (!isDragging) return;
-
- // Calculate new position based on mouse/touch position and offset
- const newLeft = clientX - dragOffsetX;
- const newTop = clientY - dragOffsetY;
-
- // Ensure the element stays within viewport bounds
- const maxX = window.innerWidth - container.offsetWidth;
- const maxY = window.innerHeight - container.offsetHeight;
-
- const boundedLeft = Math.max(0, Math.min(newLeft, maxX));
- const boundedTop = Math.max(0, Math.min(newTop, maxY));
-
- // Set new position
- container.style.left = boundedLeft + 'px';
- container.style.top = boundedTop + 'px';
- container.style.right = 'auto'; // Clear right positioning to avoid conflicts
- }
-
- // Function to end dragging
- function endDrag() {
- if (isDragging) {
- isDragging = false;
- document.body.style.userSelect = '';
-
- // Save the new position and mark as modified
- try {
- GM_setValue('switcherPosition', {
- top: container.style.top,
- right: 'auto',
- left: container.style.left
- });
- GM_setValue('positionModified', true);
- } catch (e) {
- // Silently fail if GM_setValue is not available
- }
- }
- }
-
- // Mouse event listeners
- dragHandle.addEventListener('mousedown', (e) => {
- e.preventDefault();
- startDrag(e.clientX, e.clientY);
- });
-
- document.addEventListener('mousemove', (e) => {
- drag(e.clientX, e.clientY);
- });
-
- document.addEventListener('mouseup', endDrag);
-
- // Touch event listeners
- dragHandle.addEventListener('touchstart', (e) => {
- if (e.touches.length === 1) {
- e.preventDefault();
- const touch = e.touches[0];
- startDrag(touch.clientX, touch.clientY);
- }
- });
-
- document.addEventListener('touchmove', (e) => {
- if (isDragging && e.touches.length === 1) {
- e.preventDefault();
- const touch = e.touches[0];
- drag(touch.clientX, touch.clientY);
- }
- });
-
- document.addEventListener('touchend', endDrag);
- document.addEventListener('touchcancel', endDrag);
-
- // Prevent selectbox from triggering drag
- selectBox.addEventListener('mousedown', (e) => {
- e.stopPropagation();
- });
-
- selectBox.addEventListener('touchstart', (e) => {
- e.stopPropagation();
- });
- })();