Greasy Fork is available in English.
Switch seamlessly between search engines. Supports custom engines and pinning.
// ==UserScript==
// @name Search Engine Switcher
// @name:fr Switch Moteur de Recherche
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Switch seamlessly between search engines. Supports custom engines and pinning.
// @description:fr Basculez facilement entre les moteurs de recherche. Favoris et paramètres pris en charge.
// @author adriendeval
// @match *://*.google.com/*
// @match *://*.google.ad/*
// @match *://*.google.ae/*
// @match *://*.google.al/*
// @match *://*.google.am/*
// @match *://*.google.as/*
// @match *://*.google.at/*
// @match *://*.google.az/*
// @match *://*.google.ba/*
// @match *://*.google.be/*
// @match *://*.google.bf/*
// @match *://*.google.bg/*
// @match *://*.google.bi/*
// @match *://*.google.bj/*
// @match *://*.google.bs/*
// @match *://*.google.bt/*
// @match *://*.google.by/*
// @match *://*.google.ca/*
// @match *://*.google.cd/*
// @match *://*.google.cf/*
// @match *://*.google.cg/*
// @match *://*.google.ch/*
// @match *://*.google.ci/*
// @match *://*.google.cl/*
// @match *://*.google.cm/*
// @match *://*.google.cn/*
// @match *://*.google.cv/*
// @match *://*.google.cz/*
// @match *://*.google.de/*
// @match *://*.google.dj/*
// @match *://*.google.dk/*
// @match *://*.google.dm/*
// @match *://*.google.dz/*
// @match *://*.google.ee/*
// @match *://*.google.es/*
// @match *://*.google.fi/*
// @match *://*.google.fm/*
// @match *://*.google.fr/*
// @match *://*.google.ga/*
// @match *://*.google.ge/*
// @match *://*.google.gf/*
// @match *://*.google.gg/*
// @match *://*.google.gl/*
// @match *://*.google.gm/*
// @match *://*.google.gp/*
// @match *://*.google.gr/*
// @match *://*.google.gy/*
// @match *://*.google.hn/*
// @match *://*.google.hr/*
// @match *://*.google.ht/*
// @match *://*.google.hu/*
// @match *://*.google.ie/*
// @match *://*.google.im/*
// @match *://*.google.iq/*
// @match *://*.google.is/*
// @match *://*.google.it/*
// @match *://*.google.je/*
// @match *://*.google.jo/*
// @match *://*.google.ki/*
// @match *://*.google.kg/*
// @match *://*.google.kz/*
// @match *://*.google.la/*
// @match *://*.google.li/*
// @match *://*.google.lk/*
// @match *://*.google.lt/*
// @match *://*.google.lu/*
// @match *://*.google.lv/*
// @match *://*.google.md/*
// @match *://*.google.me/*
// @match *://*.google.mg/*
// @match *://*.google.mk/*
// @match *://*.google.ml/*
// @match *://*.google.mn/*
// @match *://*.google.ms/*
// @match *://*.google.mu/*
// @match *://*.google.mv/*
// @match *://*.google.mw/*
// @match *://*.google.ne/*
// @match *://*.google.nl/*
// @match *://*.google.no/*
// @match *://*.google.nr/*
// @match *://*.google.nu/*
// @match *://*.google.pl/*
// @match *://*.google.pn/*
// @match *://*.google.ps/*
// @match *://*.google.pt/*
// @match *://*.google.ro/*
// @match *://*.google.ru/*
// @match *://*.google.rw/*
// @match *://*.google.sc/*
// @match *://*.google.se/*
// @match *://*.google.sh/*
// @match *://*.google.si/*
// @match *://*.google.sk/*
// @match *://*.google.sn/*
// @match *://*.google.so/*
// @match *://*.google.sm/*
// @match *://*.google.sr/*
// @match *://*.google.st/*
// @match *://*.google.td/*
// @match *://*.google.tg/*
// @match *://*.google.tk/*
// @match *://*.google.tl/*
// @match *://*.google.tm/*
// @match *://*.google.tn/*
// @match *://*.google.to/*
// @match *://*.google.tt/*
// @match *://*.google.vg/*
// @match *://*.google.vu/*
// @match *://*.google.ws/*
// @match *://*.google.rs/*
// @match *://*.google.co.*/*
// @match *://*.google.com.*/*
// @match *://*.bing.com/*
// @match *://search.brave.com/*
// @match *://*.duckduckgo.com/*
// @match *://*.ecosia.org/*
// @match *://*.qwant.com/*
// @match *://*.startpage.com/*
// @match *://*.yahoo.com/*
// @exclude *://mail.google.com/*
// @exclude *://drive.google.com/*
// @exclude *://docs.google.com/*
// @exclude *://keep.google.com/*
// @exclude *://photos.google.com/*
// @exclude *://calendar.google.com/*
// @exclude *://meet.google.com/*
// @exclude *://play.google.com/*
// @exclude *://translate.google.com/*
// @exclude *://maps.google.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let currentLang = GM_getValue('ses_lang', 'en');
const i18n = {
en: { title: "Search on...", settings: "⚙️ Settings", save: "Save", close: "Cancel", manage: "Manage Search Engines", language: "Language" },
fr: { title: "Rechercher sur...", settings: "⚙️ Paramètres", save: "Enregistrer", close: "Annuler", manage: "Gérer les moteurs", language: "Langue" }
};
function t(key) { return i18n[currentLang][key]; }
const defaultEngines = [
{ id: 'google', name: 'Google', domain: 'google', param: 'q', url: 'https://www.google.com/search?q=', pinned: true },
{ id: 'bing', name: 'Bing', domain: 'bing.com', param: 'q', url: 'https://www.bing.com/search?q=', pinned: true },
{ id: 'brave', name: 'Brave', domain: 'brave.com', param: 'q', url: 'https://search.brave.com/search?q=', pinned: true },
{ id: 'duckduckgo', name: 'DuckDuckGo', domain: 'duckduckgo', param: 'q', url: 'https://duckduckgo.com/?q=', pinned: true },
{ id: 'ecosia', name: 'Ecosia', domain: 'ecosia.org', param: 'q', url: 'https://www.ecosia.org/search?q=', pinned: true },
{ id: 'qwant', name: 'Qwant', domain: 'qwant.com', param: 'q', url: 'https://www.qwant.com/?q=', pinned: false },
{ id: 'startpage', name: 'Startpage', domain: 'startpage', param: 'query', url: 'https://www.startpage.com/sp/search?query=', pinned: false },
{ id: 'yahoo', name: 'Yahoo', domain: 'yahoo.com', param: 'p', url: 'https://search.yahoo.com/search?p=', pinned: false }
];
let engines = GM_getValue('ses_engines', defaultEngines);
function isSearchPage() {
const host = window.location.hostname;
// Rejette spécifiquement les sous-domaines Google hors 'www' ou racine
if (host.includes('google.') && !/^www\.google\.[a-z.]+$/.test(host) && !/^google\.[a-z.]+$/.test(host)) return false;
return true;
}
function saveSettings(newEngines, newLang) {
engines = newEngines;
currentLang = newLang;
GM_setValue('ses_engines', engines);
GM_setValue('ses_lang', currentLang);
location.reload();
}
function getSearchQuery(engine) {
const urlParams = new URLSearchParams(window.location.search);
let query = urlParams.get(engine.param);
if (query) return query;
const inputs = document.querySelectorAll(`input[name="${engine.param}"], textarea[name="${engine.param}"]`);
for (let el of inputs) {
if (el.value && el.value.trim().length > 0) return el.value;
}
if (document.title) {
const separators = [' - ', ' | '];
for(let sep of separators){
if(document.title.includes(sep)) return document.title.split(sep)[0];
}
}
return null;
}
function openSettings() {
let modal = document.getElementById('sse-settings');
if (modal) modal.remove();
modal = document.createElement('div');
modal.id = 'sse-settings';
let html = `
<div class="sse-modal-content">
<h3>${t('manage')}</h3>
<div class="sse-setting-row">
<label>${t('language')}:</label>
<select id="sse-lang-select">
<option value="en" ${currentLang === 'en' ? 'selected' : ''}>English</option>
<option value="fr" ${currentLang === 'fr' ? 'selected' : ''}>Français</option>
</select>
</div>
<div class="sse-engine-list">
`;
engines.forEach((eng, index) => {
html += `
<label class="sse-engine-item">
<input type="checkbox" data-index="${index}" ${eng.pinned ? 'checked' : ''}>
<span>${eng.name}</span>
</label>
`;
});
html += `
</div>
<div class="sse-actions">
<button id="sse-btn-close">${t('close')}</button>
<button id="sse-btn-save">${t('save')}</button>
</div>
</div>
`;
modal.innerHTML = html;
document.body.appendChild(modal);
document.getElementById('sse-btn-close').addEventListener('click', () => modal.remove());
document.getElementById('sse-btn-save').addEventListener('click', () => {
const inputs = modal.querySelectorAll('.sse-engine-list input[type="checkbox"]');
inputs.forEach(input => {
const idx = input.getAttribute('data-index');
engines[idx].pinned = input.checked;
});
const selectedLang = document.getElementById('sse-lang-select').value;
saveSettings(engines, selectedLang);
});
}
function buildWidget(currentEngineName, currentQuery) {
const old = document.getElementById('sse-container');
if (old) old.remove();
const container = document.createElement('div');
container.id = 'sse-container';
const menu = document.createElement('div');
menu.id = 'sse-menu';
menu.className = 'hidden';
let menuHtml = `<div class="sse-header">${t('title')}</div><ul id="sse-list">`;
engines.filter(e => e.pinned && e.name !== currentEngineName).forEach(eng => {
menuHtml += `<li><a href="${eng.url}${encodeURIComponent(currentQuery)}" target="_self">${eng.name}</a></li>`;
});
menuHtml += `</ul><div class="sse-settings-btn" id="sse-open-settings">${t('settings')}</div>`;
menu.innerHTML = menuHtml;
const btn = document.createElement('button');
btn.id = 'sse-btn';
btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10h14l-4-4"/><path d="M17 14H3l4 4"/></svg>`;
container.appendChild(menu);
container.appendChild(btn);
document.body.appendChild(container);
btn.addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.toggle('hidden');
});
document.getElementById('sse-open-settings').addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.add('hidden');
openSettings();
});
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) menu.classList.add('hidden');
});
}
let lastQuery = null;
function checkAndInject() {
if (!isSearchPage()) return;
const currentUrl = window.location.href;
let currentEngineObj = engines.find(eng => currentUrl.includes(eng.domain));
if (!currentEngineObj && currentUrl.includes('google.')) {
currentEngineObj = engines.find(e => e.id === 'google');
}
// Bloquer si le moteur n'est pas reconnu OU n'est pas épinglé
if (!currentEngineObj || !currentEngineObj.pinned) return;
const query = getSearchQuery(currentEngineObj);
if (query && query !== lastQuery) {
lastQuery = query;
buildWidget(currentEngineObj.name, query);
}
}
checkAndInject();
setInterval(checkAndInject, 1000);
const css = `
:root {
--sse-bg: #ffffff;
--sse-text: #202124;
--sse-hover: #f1f3f4;
--sse-accent: #1a73e8;
--sse-border: #dadce0;
--sse-shadow: 0 4px 12px rgba(0,0,0,0.15);
--sse-modal-bg: rgba(0,0,0,0.5);
}
@media (prefers-color-scheme: dark) {
:root {
--sse-bg: #303134;
--sse-text: #e8eaed;
--sse-hover: #3c4043;
--sse-accent: #8ab4f8;
--sse-border: #5f6368;
--sse-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
}
#sse-container { position: fixed; bottom: 30px; right: 30px; z-index: 2147483647; font-family: 'Google Sans', Arial, sans-serif; display: flex; flex-direction: column; align-items: flex-end; }
#sse-btn { background-color: var(--sse-bg); color: var(--sse-accent); border: 1px solid var(--sse-border); border-radius: 16px; width: 56px; height: 56px; cursor: pointer; box-shadow: var(--sse-shadow); display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
#sse-btn:hover { transform: scale(1.05); background-color: var(--sse-hover); }
#sse-menu { background-color: var(--sse-bg); border: 1px solid var(--sse-border); border-radius: 12px; box-shadow: var(--sse-shadow); margin-bottom: 12px; min-width: 180px; overflow: hidden; transform-origin: bottom right; animation: sse-fade 0.2s ease-out; }
#sse-menu.hidden { display: none; }
.sse-header { padding: 12px 16px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--sse-text); opacity: 0.7; border-bottom: 1px solid var(--sse-border); }
#sse-list { list-style: none; margin: 0; padding: 4px 0; }
#sse-list li a { display: block; padding: 10px 16px; text-decoration: none; color: var(--sse-text); font-size: 14px; transition: background 0.1s; }
#sse-list li a:hover { background-color: var(--sse-hover); color: var(--sse-accent); }
.sse-settings-btn { padding: 10px 16px; font-size: 12px; color: var(--sse-text); cursor: pointer; border-top: 1px solid var(--sse-border); background: var(--sse-bg); text-align: center; font-weight: bold; }
.sse-settings-btn:hover { background-color: var(--sse-hover); }
#sse-settings { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: var(--sse-modal-bg); z-index: 2147483647; display: flex; align-items: center; justify-content: center; font-family: Arial, sans-serif; }
.sse-modal-content { background: var(--sse-bg); padding: 24px; border-radius: 12px; width: 320px; max-width: 90%; box-shadow: var(--sse-shadow); color: var(--sse-text); }
.sse-modal-content h3 { margin-top: 0; font-size: 18px; margin-bottom: 16px; }
.sse-setting-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; font-size: 14px; }
.sse-setting-row select { padding: 4px; border-radius: 4px; border: 1px solid var(--sse-border); background: var(--sse-bg); color: var(--sse-text); }
.sse-engine-list { max-height: 250px; overflow-y: auto; margin-bottom: 20px; border: 1px solid var(--sse-border); border-radius: 8px; padding: 8px; }
.sse-engine-item { display: flex; align-items: center; padding: 8px; cursor: pointer; user-select: none; }
.sse-engine-item input { margin-right: 12px; cursor: pointer; }
.sse-engine-item:hover { background: var(--sse-hover); border-radius: 4px; }
.sse-actions { display: flex; justify-content: flex-end; gap: 10px; }
.sse-actions button { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; }
#sse-btn-close { background: transparent; color: var(--sse-text); border: 1px solid var(--sse-border); }
#sse-btn-save { background: var(--sse-accent); color: #fff; }
@keyframes sse-fade { from { opacity: 0; transform: translateY(10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
`;
GM_addStyle(css);
})();