// ==UserScript==
// @name Metasearch
// @namespace https://github.com/Jkker/metasearch-tampermonkey
// @version 1.3.0
// @description Aggregated Searcher
// @author Jkker
// @license MIT
// @match *://bing.com/search*
// @match *://*.bing.com/search*
// @match *://google.com/search*
// @match *://*.google.com/search*
// @match *://baidu.com/s*
// @match *://*.baidu.com/s*
// @match *://zhihu.com/search*
// @match *://*.zhihu.com/search*
// @match *://bilibili.com/all*
// @match *://*.bilibili.com/all*
// @match *://xiaohongshu.com/search_result/*
// @match *://*.xiaohongshu.com/search_result/*
// @match *://duckduckgo.com/*
// @match *://*.duckduckgo.com/*
// @match *://youtube.com/results*
// @match *://*.youtube.com/results*
// @match *://github.com/search*
// @match *://*.github.com/search*
// @match *://twitter.com/search*
// @match *://*.twitter.com/search*
// @match *://wolframalpha.com/input*
// @match *://*.wolframalpha.com/input*
// @match *://weibo.com/weibo*
// @match *://*.weibo.com/weibo*
// @match *://youdao.com/result*
// @match *://*.youdao.com/result*
// @match *://amazon.com/s*
// @match *://*.amazon.com/s*
// @match *://ebay.com/sch/i.html*
// @match *://*.ebay.com/sch/i.html*
// @match *://jd.com/bases/m/searchKeyword.htm*
// @match *://*.jd.com/bases/m/searchKeyword.htm*
// @match *://taobao.com/search*
// @match *://*.taobao.com/search*
// @match *://mozilla.org/en-US/search*
// @match *://*.mozilla.org/en-US/search*
// @icon https://raw.githubusercontent.com/Jkker/metasearch-tampermonkey/master/src/favicon.ico
// @grant none
// @supportURL https://github.com/Jkker/metasearch-tampermonkey/issues
// @downloadURL none
// ==/UserScript==
(function() {
"use strict";
const config = {
engines: [
{
name: "Bing",
key: "bing",
url: "https://www.bing.com/search?q=%s",
matcher: "https://www.bing.com/search",
embeddable: false,
priority: 11,
disabled: false,
lightness: 0.49,
color: "#174AE4",
background: "linear-gradient(90deg, #2870EA 10.79%, #1B4AEF 87.08%)",
icon: ''
},
{
name: "Google",
key: "google",
disabled: false,
url: "https://www.google.com/search?igu=1&pws=0&gl=us&gws_rd=cr&source=hp&newwindow=1&q=%s&oq=%s&safe=off",
matcher: /^https?:\/\/www\.google(?:\.[A-z]{2,3}){1,2}\/[^?]+\?(?!tbm=)(?:&?q=|(?:[^#](?!&tbm=))+?&q=)(?:.(?!&tbm=))*$|(^https?:\/\/xn--flw351e\.ml\/search\?q=)/i,
embeddable: true,
priority: 10,
lightness: 0.607843137254902,
color: "#4285F4",
icon: ''
},
{
name: "\u767E\u5EA6",
key: "baidu",
disabled: false,
url: "https://www.baidu.com/s?ie=utf-8&word=%s",
matcher: /^https?:\/\/www\.baidu\.com\/(?:s|baidu)/i,
embeddable: true,
priority: 9,
lightness: 0.5078431372549019,
color: "#556dea",
icon: '',
q: ["wd", "word"]
},
{
name: "\u77E5\u4E4E",
key: "zhihu",
disabled: false,
url: "https://www.zhihu.com/search?type=content&q=%s",
matcher: /^https?:\/\/www\.zhihu\.com\/search\?/i,
embeddable: false,
priority: 8,
lightness: 0.5,
color: "#0084FF",
icon: ''
},
{
name: "bilibili",
key: "bilibili",
disabled: false,
url: "https://search.bilibili.com/all?keyword=%s",
matcher: /^https?:\/\/search\.bilibili\.com\/all/i,
q: "keyword",
embeddable: true,
priority: 7,
deeplink: "bilibili://search?keyword=%s",
lightness: 0.6431372549019607,
color: "#DF698C",
icon: ''
},
{
name: "\u5C0F\u7EA2\u4E66",
key: "xiaohongshu",
disabled: false,
url: "https://www.xiaohongshu.com/search_result/?source=web_search_result_notes&keyword=%s",
matcher: /^https?:\/\/www\.xiaohongshu\.com\/search_result/i,
q: "keyword",
embeddable: false,
priority: 7,
lightness: 0.6431372549019607,
color: "#ff2e4d",
icon: ''
},
{
name: "DDG",
key: "duckduckgo",
url: "https://duckduckgo.com/?q=%s&kaj=m&k1=-1&kn=1&kp=-2",
matcher: /^https?:\/\/duckduckgo\.com\/*/i,
embeddable: true,
priority: 6,
disabled: false,
lightness: 0.6039215686274509,
color: "#E37151",
icon: ''
},
{
name: "Reddit",
key: "reddit",
disabled: false,
url: "https://www.google.com/search?q=%s+site%3Areddit.com",
matcher: (url2, query) => {
var _a, _b, _c;
return url2.includes("google.com") && ((_c = (_b = decodeURIComponent((_a = query.get("q")) != null ? _a : "")) == null ? void 0 : _b.includes) == null ? void 0 : _c.call(_b, "site:reddit.com"));
},
q: (url2, query) => {
var _a, _b, _c;
return (_c = (_b = decodeURIComponent((_a = query.get("q")) != null ? _a : "")).replace) == null ? void 0 : _c.call(
_b,
"site:reddit.com",
""
);
},
embeddable: true,
priority: 6,
lightness: 0.5372549019607843,
color: "#eb5527",
icon: ''
},
{
name: "YouTube",
key: "youtube",
disabled: false,
url: "https://www.youtube.com/results?search_query=%s",
matcher: /^https?:\/\/www\.youtube\.com\/results/i,
q: "search_query",
embeddable: true,
priority: 6,
deeplink: "youtube://YouTube.com/results?search_query=%s",
lightness: 0.5254901960784314,
color: "#ea3322",
icon: ''
},
{
name: "Github",
key: "github",
disabled: false,
url: "https://github.com/search?q=%s",
matcher: /^https?:\/\/github\.com\/search/i,
embeddable: true,
priority: 5,
lightness: 0.09215686274509804,
color: "#181717",
icon: ''
},
{
name: "Twitter",
key: "twitter",
disabled: false,
url: "https://twitter.com/search?q=%s",
matcher: /^https?:\/\/twitter\.com\/search/i,
embeddable: true,
priority: 2,
lightness: 0.5254901960784314,
color: "#1da1f2",
icon: ''
},
{
name: "Wolfram",
key: "wolfram",
disabled: false,
url: "https://www.wolframalpha.com/input?i=%s",
matcher: /^https?:\/\/www\.wolframalpha\.com\/input/i,
embeddable: true,
priority: 4,
lightness: 0.5254901960784314,
color: "#e87242",
icon: '',
q: "i"
},
{
name: "\u4ECA\u65E5\u5934\u6761",
key: "toutiao",
disabled: true,
url: "https://so.toutiao.com/search?keyword=%s",
matcher: /^https?:\/\/so\.toutiao\.com\/search/i,
q: "keyword",
embeddable: true,
priority: 4,
lightness: 0.5764705882352941,
color: "#dd5049",
icon: ''
},
{
name: "\u5FAE\u535A",
key: "weibo",
disabled: false,
url: "https://s.weibo.com/weibo?q=%s",
matcher: /^https?:\/\/(s|m)\.weibo\.c(om|n)\/(weibo|search)/i,
q: (url2, query) => {
var _a, _b, _c;
if (url2.match(/^https?:\/\/s\.weibo\.com\/weibo/i)) {
return (_a = query.get("q")) != null ? _a : "";
}
return (_c = new URLSearchParams(
decodeURIComponent((_b = query.get("containerid")) != null ? _b : "")
).get("q")) != null ? _c : "";
},
embeddable: true,
priority: 3.5,
deeplink: "sinaweibo://searchall?q=%s",
lightness: 0.5156862745098039,
color: "#d33436",
icon: ''
},
{
name: "Quora",
key: "quora",
url: "https://www.google.com/search?q=%s+site%3Aquora.com",
matcher: (url2, query) => {
var _a, _b, _c;
return url2.includes("google.com") && ((_c = (_b = decodeURIComponent((_a = query.get("q")) != null ? _a : "")) == null ? void 0 : _b.includes) == null ? void 0 : _c.call(_b, "site:quora.com"));
},
q: (url2, query) => {
var _a, _b, _c;
return (_c = (_b = decodeURIComponent((_a = query.get("q")) != null ? _a : "")).replace) == null ? void 0 : _c.call(
_b,
"site:quora.com",
""
);
},
embeddable: true,
priority: 3,
disabled: true,
lightness: 0.42549019607843136,
color: "#aa382f",
icon: ''
},
{
name: "\u8BCD\u5178",
key: "bingdict",
url: "https://cn.bing.com/dict/search?q=%s",
matcher: "https://cn.bing.com/dict/search",
embeddable: false,
priority: 3,
disabled: true,
lightness: 0.6,
color: "#09ABA0",
icon: ''
},
{
name: "\u6709\u9053",
key: "youdao",
disabled: false,
url: "https://dict.youdao.com/result?word=%s&lang=en",
matcher: /^https?:\/\/dict\.youdao\.com\/(m\/)?result/i,
q: "word",
embeddable: true,
priority: 3,
lightness: 0.484313725490196,
color: "#E31436",
icon: ''
},
{
name: "Amazon",
key: "amazon",
disabled: false,
url: "https://www.amazon.com/s?k=%s",
matcher: "amazon.com/s",
q: "k",
embeddable: true,
priority: 2,
lightness: 0.5,
color: "#FF9900",
icon: ''
},
{
name: "eBay",
key: "ebay",
url: "https://www.ebay.com/sch/i.html?_nkw=%s",
matcher: "ebay.com/sch",
q: "_nkw",
embeddable: true,
priority: 2,
disabled: false,
lightness: 0.4862745098039215,
color: "#4164ea",
icon: ''
},
{
name: "\u4EAC\u4E1C",
key: "jing-dong",
disabled: false,
url: "https://sou.m.jd.com/bases/m/searchKeyword.htm?keyword=%s",
matcher: "sou.m.jd.com/bases/m/searchKeyword.htm",
q: "keyword",
embeddable: true,
priority: 1,
deeplink: 'openapp.jdmobile://virtual?params={"des":"productList","keyWord":"%s","from":"search","category":"jump"}',
lightness: 0.4941176470588235,
color: "#E1251B",
icon: ''
},
{
name: "\u6DD8\u5B9D",
key: "taobao",
disabled: false,
url: "https://s.taobao.com/search?q=%s",
matcher: "s.taobao.com/search",
embeddable: true,
priority: 1,
deeplink: "taobao://s.taobao.com?q=%s",
lightness: 0.5196078431372548,
color: "#E94F20",
icon: ''
},
{
name: "MDN",
key: "mdnwebdocs",
disabled: false,
url: "https://developer.mozilla.org/en-US/search?q=%s",
matcher: "developer.mozilla.org/en-US/search",
embeddable: true,
priority: -1,
lightness: 0,
color: "#000000",
icon: ''
},
{
name: "\u5FAE\u4FE1",
key: "wechat",
disabled: true,
url: "https://weixin.sogou.com/weixin?p=01030402&query=%s&type=2&ie=utf8",
matcher: "weixin.sogou.com/weixin",
q: "query",
embeddable: true,
priority: -2,
lightness: 0.39215686274509803,
color: "#07C160",
icon: ''
}
]
};
const styles = "";
const throttle = (callback, limit = 100) => {
let waiting = false;
return (...args) => {
if (!waiting) {
callback.apply(null, args);
waiting = true;
setTimeout(() => {
waiting = false;
}, limit);
}
};
};
const lightnessOfHexColor = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result)
return 0.5;
const r = parseInt(result[1], 16) / 255;
const g = parseInt(result[2], 16) / 255;
const b = parseInt(result[3], 16) / 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
const l = (max + min) / 2;
return l;
};
const isMobileDevice = /Mobi/i.test(window.navigator.userAgent);
const engines = config.engines.filter((e) => !e.disabled).sort((a, b) => b.priority - a.priority);
const hotkeys = engines.reduce((obj, engine, index) => {
const key = engine.key[0].toLowerCase();
obj[key] = obj[key] ? [...obj[key], index] : [index];
return obj;
}, {});
function Button({
icon,
color,
background,
name,
lightness,
url: url2,
deeplink,
index,
searchQuery = ""
}) {
const href = (isMobileDevice ? deeplink || url2 : url2).replaceAll("%s", searchQuery);
const a = Object.assign(document.createElement("a"), {
title: name,
href,
target: "_blank",
rel: "noopener noreferrer",
"aria-label": name,
"data-index": String(index),
innerHTML: icon + `${name}`,
className: "icon-button"
});
if (color)
a.style.setProperty("--color", color);
if (background)
a.style.setProperty("--background", background);
if ((lightness != null ? lightness : lightnessOfHexColor(color)) < 0.5)
a.classList.add("dark-invert");
return a;
}
const getCurrentEngineIndex = (url2, searchParams) => {
for (let i = engines.length - 1; i >= 0; i--) {
const e = engines[i];
if (!e.matcher)
continue;
if (e.matcher instanceof RegExp) {
if (e.matcher.test(url2)) {
return i;
}
} else if (typeof e.matcher === "function") {
try {
if (e.matcher(url2, searchParams)) {
return i;
}
} catch (e2) {
console.error(e2);
}
} else if (typeof e.matcher === "string") {
if (url2.includes(e.matcher)) {
return i;
}
}
}
return -1;
};
const getSearchQuery = (engine, url2, searchParams) => {
if (typeof engine.q === "string") {
return searchParams.get(engine.q);
}
if (Array.isArray(engine.q)) {
for (const param of engine.q) {
const q = searchParams.get(param);
if (q)
return q;
}
}
if (engine.q instanceof RegExp) {
const match = engine.q.exec(window.location.href);
if (match)
return match[1];
}
if (typeof engine.q === "function") {
return engine.q(url2, searchParams);
}
return searchParams.get("q") || searchParams.get("query") || void 0;
};
const url = window.location.href;
const params = new URLSearchParams(window.location.search);
const currEngineIndex = getCurrentEngineIndex(url, params);
if (currEngineIndex !== -1) {
const filtered = engines.filter((_, i) => i !== currEngineIndex);
const matchedEngine = engines[currEngineIndex];
let searchQuery = getSearchQuery(matchedEngine, url, params);
if (searchQuery) {
const body = document.querySelector("body");
const root = document.createElement("div");
const linkContainer = document.createElement("div");
linkContainer.id = "metasearch-link-container";
root.id = "metasearch-root";
let prevScrollPosition = window.scrollY;
window.addEventListener(
"scroll",
throttle(() => {
const currentScrollPos = window.scrollY;
if (prevScrollPosition < currentScrollPos) {
root.style.bottom = "-48px";
} else {
root.style.bottom = "0";
}
prevScrollPosition = currentScrollPos;
}, 100),
true
);
const linkList = [];
filtered.forEach((engine, index) => {
const button = Button({
...engine,
index,
searchQuery: encodeURIComponent(searchQuery.trim())
});
linkList.push(button);
linkContainer.appendChild(button);
});
root.appendChild(linkContainer);
root.appendChild(
Object.assign(document.createElement("button"), {
title: "Close",
"aria-label": "Close",
innerHTML: ``,
className: "icon-button",
id: "metasearch-close",
onclick: () => {
root.style.bottom = "-40px";
}
})
);
function styleInject(css,ref){if(ref===void 0){ref={}}var insertAt=ref.insertAt;if(!css||typeof document==="undefined"){return}var head=document.head||document.getElementsByTagName("head")[0];var style=document.createElement("style");style.type="text/css";if(insertAt==="top"){if(head.firstChild){head.insertBefore(style,head.firstChild)}else{head.appendChild(style)}}else{head.appendChild(style)}if(style.styleSheet){style.styleSheet.cssText=css}else{style.appendChild(document.createTextNode(css))}};styleInject(`#metasearch-root {
box-sizing: border-box;
width: 100vw;
display: flex;
position: fixed;
bottom: 0;
left: 0;
transition: all 0.1s ease-in-out;
height: 32px;
background-color: rgba(255, 255, 255, 0.8);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
z-index: 999999999;
box-shadow: 0px 0px 9px rgba(0, 0, 0, 0.07);
overflow-y: hidden;
}
#metasearch-root .icon-button {
all: unset;
box-sizing: border-box;
text-decoration: none;
color: var(--color);
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
background-color: transparent;
border: 0;
outline: transparent;
cursor: pointer;
transition: all 0.15s ease-in-out;
white-space: nowrap;
-webkit-tap-highlight-color: transparent;
flex: 1 0 auto;
padding: 2px 5px;
min-width: 32px;
height: 100%;
}
#metasearch-root .icon-button > svg {
width: 20px;
height: 20px;
color: currentColor;
fill: currentColor;
stroke: currentColor;
}
@media screen and (max-width: 768px) {
#metasearch-root .icon-button > svg {
height: 24px;
width: 24px;
}
}
#metasearch-root .icon-button:hover {
filter: brightness(0.85);
background: rgba(0, 0, 0, 0.08);
}
@media screen and (prefers-color-scheme: dark) {
#metasearch-root .icon-button:hover.dark-invert {
filter: invert(1) hue-rotate(180deg) brightness(0.85);
}
}
#metasearch-root .icon-button:active {
-webkit-tap-highlight-color: transparent;
filter: brightness(0.9);
color: white;
background: var(--color);
}
@media screen and (prefers-color-scheme: dark) {
#metasearch-root .icon-button:active.dark-invert {
filter: invert(1) hue-rotate(180deg) brightness(0.9);
}
}
#metasearch-root .icon-button:focus {
-webkit-tap-highlight-color: transparent;
color: white;
background: var(--color);
}
@media screen and (prefers-color-scheme: dark) {
#metasearch-root .icon-button:focus.dark-invert {
filter: invert(1) hue-rotate(180deg) brightness(0.7);
}
}
@media screen and (max-width: 768px) {
#metasearch-root .icon-button {
padding: 0;
min-width: 40px;
}
#metasearch-root .icon-button > span {
display: none;
}
}
@media screen and (max-width: 768px) {
#metasearch-root {
height: 40px;
}
}
@media screen and (prefers-color-scheme: dark) {
#metasearch-root {
background-color: rgba(25, 25, 25, 0.7);
color: rgba(255, 255, 255, 0.8);
}
#metasearch-root .dark-invert {
filter: invert(1) hue-rotate(180deg);
}
}
#metasearch-root #metasearch-close {
box-shadow: -1px 2px 9px rgba(0, 0, 0, 0.1);
}
#metasearch-root #metasearch-link-container {
scrollbar-width: none;
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
width: 100%;
box-sizing: border-box;
}
#metasearch-root #metasearch-link-container::-webkit-scrollbar {
display: none;
}
body {
position: relative !important;
}`);
const getNextTabIndex = (currIndex = -1, key) => {
for (let i = currIndex + 1; i < filtered.length + currIndex; i++) {
const index = i % filtered.length;
if (filtered[index].key[0] === key.toLowerCase())
return index;
}
return currIndex;
};
const keydownListener = (e) => {
if (e.key === "Alt") {
root.style.bottom = "0";
}
const active = document.activeElement;
if (e.key === "Escape") {
if (root.contains(active)) {
e.preventDefault();
active.blur();
return;
}
}
const key = e.key.toLowerCase();
const focusIndex = linkContainer.contains(active) ? parseInt(active.getAttribute("data-index") || "-1", 10) : -1;
if (e.altKey && hotkeys[key] !== void 0) {
e.preventDefault();
const next = getNextTabIndex(focusIndex, key);
linkList[next].focus();
return;
}
const num = parseInt(e.key, 10);
if (e.altKey && !isNaN(num) && num < filtered.length) {
e.preventDefault();
const index = num - 1;
linkList[index].focus();
return;
}
if (e.altKey && (e.key === "[" || e.key === "-")) {
const prevIndex = focusIndex - 1 < 0 ? filtered.length - 1 : focusIndex - 1;
linkList[prevIndex].focus();
return;
}
if (e.altKey && (e.key === "]" || e.key === "=")) {
const nextIndex = (focusIndex + 1) % filtered.length;
linkList[nextIndex].focus();
return;
}
};
const keyUpListener = (e) => {
const active = document.activeElement;
if (e.key === "Alt" && linkContainer.contains(active)) {
active.click();
active.blur();
}
};
document.addEventListener("keydown", keydownListener);
document.addEventListener("keyup", keyUpListener);
linkContainer.addEventListener(
"wheel",
throttle((event) => {
if (!event.deltaY)
return;
linkContainer.scrollBy({
left: event.deltaY * 4,
behavior: "smooth"
});
event.preventDefault();
event.stopPropagation();
}, 100)
);
body.appendChild(root);
}
}
})();