// ==UserScript==
// @name 聚合搜索引擎切换导航[自改]
// @namespace http://tampermonkey.net/
// @icon https://s2.loli.net/2025/03/08/OCtScJhM1biHEfB.png
// @version 2025.04.28
// @description 在搜索结果页顶部或选中文本时显示一个聚合搜索引擎切换导航栏,方便在不同引擎间跳转。专注于移动端优化。
// @author PunkJet,tutrabbit,Gemini
// @match *://*/*
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-body
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/529210/%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%E5%88%87%E6%8D%A2%E5%AF%BC%E8%88%AA%5B%E8%87%AA%E6%94%B9%5D.user.js
// @updateURL https://update.greasyfork.icu/scripts/529210/%E8%81%9A%E5%90%88%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%E5%88%87%E6%8D%A2%E5%AF%BC%E8%88%AA%5B%E8%87%AA%E6%94%B9%5D.meta.js
// ==/UserScript==
(function () {
"use strict"; // 启用严格模式
// --- 配置区域 ---
// 默认在导航栏显示的搜索引擎名称列表(使用 '-' 分隔)
const DEFAULT_ENGINES_ORDER = "必应-百度-谷歌-头条-F搜-夸克-搜狗-360";
// 非搜索页面选中文本后工具栏自动隐藏的延迟时间(毫秒)
const AUTO_HIDE_DELAY = 5000; // 默认5秒
// 所有支持的搜索引擎名称列表(用于设置提示)
const ALL_SUPPORTED_ENGINES =
"必应-百度-谷歌-知乎-F搜-360-夸克-搜狗-头条-Yandex-Ecosia-DuckDuckGo-QwantLite-Swisscows";
// 用户自定义排序设置在 GM 存储中的键名
const GM_STORAGE_KEY = "punk_setup_search";
// --- 搜索引擎配置 (保持不变) ---
const SEARCH_ENGINES = [
{
name: "必应",
searchUrl: "https://www.bing.com/search?q=",
searchkeyName: ["q"],
matchUrl: /bing\.com.*?search\?q=/i,
},
{
name: "百度",
searchUrl: "https://baidu.com/s?wd=",
searchkeyName: ["wd", "word"],
matchUrl: /baidu\.com.*?w(or)?d=/i,
},
{
name: "谷歌",
searchUrl: "https://www.google.com/search?q=",
searchkeyName: ["q"],
matchUrl: /google\..*?\/search.*?q=/i,
},
{
name: "知乎",
searchUrl: "https://www.zhihu.com/search?q=",
searchkeyName: ["q"],
matchUrl: /zhihu\.com\/search.*?q=/i,
},
{
name: "F搜",
searchUrl: "https://fsoufsou.com/search?q=",
searchkeyName: ["q"],
matchUrl: /fsoufsou\.com\/.*?q=/i,
},
{
name: "360",
searchUrl: "https://www.so.com/s?q=",
searchkeyName: ["q"],
matchUrl: /\.so\.com.*?q=/i,
},
{
name: "夸克",
searchUrl: "https://quark.sm.cn/s?q=",
searchkeyName: ["q"],
matchUrl: /sm\.cn.*?q=/i,
},
{
name: "搜狗",
searchUrl: "https://m.sogou.com/web/searchList.jsp?keyword=",
searchkeyName: ["keyword"],
matchUrl: /sogou\.com.*?keyword=/i,
},
{
name: "头条",
searchUrl: "https://so.toutiao.com/search/?keyword=",
searchkeyName: ["keyword"],
matchUrl: /toutiao\.com.*?keyword=/i,
},
{
name: "Yandex",
searchUrl: "https://yandex.com/search/touch/?text=",
searchkeyName: ["text"],
matchUrl: /((ya(ndex)?\.ru)|(yandex\.com)).*?text=/i,
},
{
name: "DuckDuckGo",
searchUrl: "https://duckduckgo.com/?q=",
searchkeyName: ["q"],
matchUrl: /duckduckgo\.com.*?q=/i,
},
{
name: "Ecosia",
searchUrl: "https://www.ecosia.org/search?q=",
searchkeyName: ["q"],
matchUrl: /ecosia\.org.*?q=/i,
},
{
name: "QwantLite",
searchUrl: "https://lite.qwant.com/?q=",
searchkeyName: ["q"],
matchUrl: /lite\.qwant\.com.*?q=/i,
},
{
name: "Swisscows",
searchUrl: "https://swisscows.com/en/web?query=",
searchkeyName: ["query"],
matchUrl: /swisscows\.com.*?query=/i,
},
];
// --- 社交及其他站点分类配置 (保持不变) ---
const SOCIAL_SITES = [
{
tabName: "日常",
tabList: [
{ name: "知乎", searchUrl: "https://www.zhihu.com/search?q=" },
{ name: "豆瓣", searchUrl: "https://m.douban.com/search/?query=" },
{
name: "微博",
searchUrl: "https://m.weibo.cn/search?containerid=100103&q=",
},
{
name: "哔哩哔哩",
searchUrl: "https://m.bilibili.com/search?keyword=",
},
{ name: "维基百科", searchUrl: "https://zh.m.wikipedia.org/wiki/" },
{ name: "安娜档案", searchUrl: "https://annas-archive.org/search?q=" },
{ name: "Unsplash", searchUrl: "https://unsplash.com/s/photos/" },
{
name: "火山翻译",
searchUrl: "https://translate.volcengine.com/mobile?text=",
},
{ name: "博客园", searchUrl: "https://zzk.cnblogs.com/s?w=" },
],
},
{
tabName: "娱乐",
tabList: [
{ name: "知乎", searchUrl: "https://www.zhihu.com/search?q=" },
{ name: "豆瓣", searchUrl: "https://m.douban.com/search/?query=" },
{
name: "微博",
searchUrl: "https://m.weibo.cn/search?containerid=100103&q=",
},
{
name: "哔哩哔哩",
searchUrl: "https://m.bilibili.com/search?keyword=",
},
{
name: "小红书",
searchUrl: "https://m.sogou.com/web/xiaohongshu?keyword=",
},
{
name: "微信文章",
searchUrl: "https://weixin.sogou.com/weixinwap?type=2&query=",
},
{ name: "推特", searchUrl: "https://mobile.twitter.com/search?q=" },
{ name: "豆瓣阅读", searchUrl: "https://read.douban.com/search?q=" },
{
name: "Malavida",
searchUrl: "https://www.malavida.com/en/android/s/",
},
{ name: "ApkPure", searchUrl: "https://m.apkpure.com/search?q=" },
{ name: "安娜档案", searchUrl: "https://annas-archive.org/search?q=" },
{ name: "人人影视", searchUrl: "https://www.renren.pro/search?wd=" },
{ name: "豌豆Pro", searchUrl: "https://wandou.la/search/" },
],
},
{
tabName: "开发",
tabList: [
{
name: "开发者搜索",
searchUrl: "https://kaifa.baidu.com/searchPage?wd=",
},
{ name: "GitHub", searchUrl: "https://github.com/search?q=" },
{ name: "Gitee", searchUrl: "https://search.gitee.com/?q=" },
{
name: "Stackoverflow",
searchUrl: "https://stackoverflow.com/search?q=",
},
{ name: "GreasyFork", searchUrl: "https://greasyfork.org/scripts?q=" },
{ name: "MDN", searchUrl: "https://developer.mozilla.org/search?q=" },
{ name: "菜鸟教程", searchUrl: "https://www.runoob.com/?s=" },
{ name: "掘金", searchUrl: "https://juejin.cn/search?query=" },
{ name: "博客园", searchUrl: "https://zzk.cnblogs.com/s?w=" },
],
},
{
tabName: "网盘",
tabList: [
{ name: "阿里云盘", searchUrl: "https://alipansou.com/search?k=" },
{ name: "百度云盘", searchUrl: "https://xiongdipan.com/search?k=" },
{ name: "夸克网盘", searchUrl: "https://aipanso.com/search?k=" },
{
name: "罗马网盘",
searchUrl: "https://www.luomapan.com/#/main/search?keyword=",
},
],
},
{
tabName: "翻译",
tabList: [
{ name: "有道词典", searchUrl: "https://youdao.com/m/result?word=" },
{ name: "必应翻译", searchUrl: "https://cn.bing.com/dict/search?q=" },
{ name: "百度翻译", searchUrl: "https://fanyi.baidu.com/#zh/en/" },
{
name: "谷歌翻译",
searchUrl: "https://translate.google.com/?sl=auto&tl=auto&text=",
},
{
name: "火山翻译",
searchUrl: "https://translate.volcengine.com/mobile?text=",
},
{
name: "DeepL翻译",
searchUrl: "https://www.deepl.com/translator-mobile#auto/auto/",
},
],
},
{
tabName: "图片",
tabList: [
{
name: "谷歌搜图",
searchUrl: "https://www.google.com/search?tbm=isch&q=",
},
{
name: "必应搜图",
searchUrl: "https://www.bing.com/images/search?q=",
},
{ name: "Flickr", searchUrl: "https://www.flickr.com/search/?text=" },
{
name: "Pinterest",
searchUrl: "https://www.pinterest.com/search/pins/?q=",
},
{ name: "Pixabay", searchUrl: "https://pixabay.com/images/search/" },
{ name: "花瓣", searchUrl: "https://huaban.com/search/?q=" },
{ name: "Unsplash", searchUrl: "https://unsplash.com/s/photos/" },
],
},
];
// --- 辅助函数 ---
/**
* 创建并返回一个指定标签名和属性的 HTML 元素。
* @param {string} tagName - HTML 标签名 (e.g., 'div', 'a').
* @param {object} [attributes={}] - 一个包含属性键值对的对象 (e.g., { id: 'myId', class: 'myClass' }).
* @param {string} [innerHTML=''] - 元素的 innerHTML 内容。
* @returns {HTMLElement} 创建的 HTML 元素。
*/
function createElement(tagName, attributes = {}, innerHTML = "") {
const element = document.createElement(tagName);
for (const key in attributes) {
if (Object.hasOwnProperty.call(attributes, key)) {
element.setAttribute(key, attributes[key]);
}
}
if (innerHTML) {
element.innerHTML = innerHTML;
}
return element;
}
/**
* 从当前 URL 中提取搜索关键词。
* 遍历 SEARCH_ENGINES 配置,匹配当前 URL 并查找对应的搜索参数。
* @returns {string} 提取到的关键词 (已解码),如果未找到则返回空字符串。
*/
function getKeywordsFromUrl() {
const currentUrl = window.location.href;
try {
const urlParams = new URLSearchParams(window.location.search);
for (const engine of SEARCH_ENGINES) {
// 使用 try catch 包裹 matchUrl 匹配,防止正则错误导致脚本中断
try {
if (currentUrl.match(engine.matchUrl)) {
for (const key of engine.searchkeyName) {
if (urlParams.has(key)) {
return decodeURIComponent(urlParams.get(key) || "");
}
}
break; // 匹配到引擎但未找到 key,也跳出循环
}
} catch (regexError) {
console.error(
`聚合搜索:匹配引擎 ${engine.name} 的正则表达式出错:`,
regexError
);
// 可以选择继续检查下一个引擎或直接返回
continue;
}
}
} catch (urlError) {
console.error("聚合搜索:解析 URL 参数时出错:", urlError);
}
return "";
}
/**
* 获取用户配置的或默认的搜索引擎显示顺序。
* !!! 添加了对 GM_getValue 的可用性检查 !!!
* @returns {string[]} 搜索引擎名称数组。
*/
function getDisplayEngineNames() {
let storedValue = DEFAULT_ENGINES_ORDER; // 默认值
try {
// 检查 GM_getValue 是否可用且为函数类型
if (typeof GM_getValue === "function") {
storedValue = GM_getValue(GM_STORAGE_KEY, DEFAULT_ENGINES_ORDER);
} else {
console.warn(
"聚合搜索:GM_getValue 不可用或不是函数,将使用默认引擎排序。脚本是否在 Tampermonkey 等环境中运行?"
);
}
} catch (e) {
// 捕获调用 GM_getValue 时可能发生的其他错误
console.error(
"聚合搜索:读取 GM_getValue 时发生错误,将使用默认引擎排序。",
e
);
}
// 确保后续处理逻辑使用有效的值(读取到的或默认值)
return storedValue
.split("-")
.map((name) => name.trim())
.filter(Boolean);
}
/**
* 构建包含搜索链接的
元素列表。
* @param {Array