优化手机bing搜索
// ==UserScript==
// @name 优化手机bing搜索
// @namespace f+bing
// @version 2025.04.08
// @description bing搜索结果自动翻页+网址黑名单+自定义屏蔽关键词
// @icon data:image/webp;base64,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
// @author f+
// @match https://cn.bing.com/search*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant window.onurlchange
// @grant unsafeWindow
// @run-at document-end
// @connect
// @noframes
// ==/UserScript==
(function () {
"use strict";
GM_addStyle(`
.banlist-block-btn{
border: 0px;
border-radius: 0.3rem;
background-color: #eee;
color: #fff;
padding: 0px;
transition: 1.5s;
opacity: 0.6;
position: absolute;
top: 0.5rem;
right: 0.5rem;
height: 2.5rem;
width:2.5rem;
font-size:1.5rem;
font-weight:bold;
z-index: 2;
}
.banlist-config-modal {
animation: fadeInn 0.4s;
position: fixed;
top: 8%;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
width: 84%;
}
.banlist-add-container {
margin: 15px 0;
color: #777;
}
.banlist-add-textarea {
box-sizing:border-box;
width: 100%;
border: none;
background:#eee;
padding: 10px;
height: 10rem;
resize: vertical;
color: #222;
}
.banlist-add-textarea::placeholder {
color: #aaa;
}
.banlist-button-group {
text-align: right;
margin-top: 20px;
}
.banlist-action-btn {
padding: 0.5rem 2rem;
margin-left: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: #999;
color: white;
}
.banlist-action-btn.confirm {
background: #007bff;
}
.banlist-action-btn.cancel {
background: #eee;
color: #777;
}
.loader {
width: 30%;
height: 10px;
margin-bottom: 2px;
background: linear-gradient(#a6a698 0 0) left / 20px 10px no-repeat #e2e2e2;
animation: l1 3s infinite linear;
justify-self: center;
transition: transform 0.2s;
}
.loader.done{
transform: scaleY(0);
}
/* 样式补丁 */
#b_results .b_tpcn .tilk{
background-color: unset !important;
}
@keyframes l1 {
50% {background-position: right }
}
@keyframes fadeInn {
from { opacity: 0 ;transform: translate(-50%, 10%);}
to { opacity: 1 ;transform: translate(-50%, 0);}
}
`);
const banlistData = {
_cache: {
["bingCite" /* GM_KEY.CITE */]: new Set(GM_getValue("bingCite" /* GM_KEY.CITE */, [])),
["global" /* GM_KEY.WORD */]: new Set(GM_getValue("global" /* GM_KEY.WORD */, [])),
},
has(item, dataType) {
if (!item || item.length === 0)
return false;
for (let blackWord of this._cache[dataType]) {
if (item.includes(blackWord))
return true;
}
return false;
},
add(item, dataType) {
if (!item)
return;
this._cache[dataType].add(item);
GM_setValue(dataType, [...this._cache[dataType]]);
},
};
//-------------------------/ 创建配置窗口 /-------------------------//
function createConfigWindow() {
const modal = document.createElement("div");
modal.className = "banlist-config-modal";
// 添加屏蔽区域
const addSection = document.createElement("div");
addSection.className = "banlist-add-container";
const label = document.createElement("label");
label.textContent = "添加屏蔽词:";
const textArea = document.createElement("textarea");
textArea.className = "banlist-add-textarea";
textArea.placeholder = "每行输入一个屏蔽词";
addSection.append(label, textArea);
// 操作按钮
const btnGroup = document.createElement("div");
btnGroup.className = "banlist-button-group";
const confirmBtn = document.createElement("banlist-button");
confirmBtn.className = `banlist-action-btn confirm`;
confirmBtn.textContent = "保存";
confirmBtn.onclick = () => {
saveConfig(textArea);
modal.remove();
};
const cancelBtn = document.createElement("banlist-button");
cancelBtn.className = `banlist-action-btn cancel`;
cancelBtn.textContent = "取消";
cancelBtn.onclick = () => modal.remove();
btnGroup.append(confirmBtn, cancelBtn);
modal.append(addSection, btnGroup);
document.body.appendChild(modal);
}
function saveConfig(textArea) {
const newBlocked = textArea.value
.split("\n")
.map((v) => v.trim())
.filter((v) => v.length > 0);
newBlocked.forEach((word) => banlistData.add(word, "global" /* GM_KEY.WORD */));
}
//-------------------------/ 核心处理函数 /-------------------------//
const processedNodes = new WeakSet(); // 存储已处理节点
// 检测是否加载完,同时处理屏蔽条目
function scanFisrtPage() {
selectResults(document).forEach((ele) => {
if (processedNodes.has(ele))
return;
processedNodes.add(ele);
if (isShouldBan(ele)) {
removeElement(ele);
}
else {
addBlockButton(ele);
setNewTabOpen(ele);
}
});
}
function parseNewResults(doc) {
const goodResults = [];
selectResults(doc).forEach((ele) => {
if (processedNodes.has(ele))
return;
processedNodes.add(ele);
if (!isShouldBan(ele)) {
addBlockButton(ele);
transRmsImage(ele);
setNewTabOpen(ele);
goodResults.push(ele);
}
});
return goodResults;
}
function autoLoadPage() {
let loadNextPage;
let obPageReady;
function initLoader() {
// 确保每次初始化前断开已有监听
if (loadNextPage)
window.removeEventListener("scroll", loadNextPage);
if (obPageReady)
obPageReady.disconnect();
loadNextPage = getNextPageLoader();
obPageReady = new MutationObserver(() => {
const _isComplete = isFirstPageComplete();
// 需要先检测是否加载完再scan,因为scan会破坏检测条件
scanFisrtPage();
if (_isComplete) {
loadNextPage();
window.addEventListener("scroll", loadNextPage);
obPageReady.disconnect();
obPageReady = null;
}
});
obPageReady.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
});
}
// 拦截 pushState 和 replaceState 方法
if (window.onurlchange === undefined) {
(function (history) {
history.pushState = ((fn) => function () {
const result = fn.apply(history, arguments);
window.dispatchEvent(new Event("pushstate"));
window.dispatchEvent(new Event("urlchange"));
return result;
})(history.pushState);
history.replaceState = ((fn) => function () {
const result = fn.apply(history, arguments);
window.dispatchEvent(new Event("replacestate"));
window.dispatchEvent(new Event("urlchange"));
return result;
})(history.replaceState);
window.addEventListener("popstate", () => {
window.dispatchEvent(new Event("urlchange"));
});
})(window.history);
}
window.addEventListener("urlchange", () => {
console.log(`URL changed: ${location.href}`);
initLoader();
});
initLoader();
}
function getNextPageLoader() {
var _a, _b;
let curUrl = location.href;
let curDocument = document;
let isLoading = false;
const loadingElement = document.createElement("div");
loadingElement.className = "loader done";
(_b = (_a = document.body.querySelector("#b_results")) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.append(loadingElement);
function startLoad() {
isLoading = true;
loadingElement.classList.remove("done");
}
function endLoad() {
isLoading = false;
loadingElement.classList.add("done");
}
return async function loadNextPage() {
var _a;
if (isLoading)
return;
startLoad();
const isScrollBottom = (() => {
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.body.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
const isScrollBottom = scrollTop + clientHeight >= scrollHeight - 600;
return isScrollBottom;
})();
if (!isScrollBottom) {
endLoad();
return;
}
try {
const nextUrl = getNextUrl(curUrl, curDocument);
const nextDocument = await getDocument(nextUrl, { anonymous: true });
const goodResults = parseNewResults(nextDocument);
(_a = document.body.querySelector("#b_results")) === null || _a === void 0 ? void 0 : _a.append(...goodResults);
// 处理新的结果
curUrl = nextUrl;
curDocument = nextDocument;
}
catch (_b) {
console.log("读取下一页失败!");
}
endLoad();
loadNextPage();
};
}
//-------------------------/ 工具函数 /-------------------------//
// 提取搜索结果的条目
function selectResults(doc) {
const selector = "#b_results> *:not(#progContPlaceHolder)";
return doc.querySelectorAll(selector);
}
// 解析条目的引用地址
function parseItemCite(ele) {
const cite = ele.querySelector(".b_attribution cite");
if (cite)
return cite.textContent;
const link = ele.querySelector("a[href]");
if (link)
return link.getAttribute("href");
return null;
}
// 判断条目
function isShouldBan(ele) {
var _a, _b, _c, _d, _e;
// 底部的通知
if (((_a = ele.classList) === null || _a === void 0 ? void 0 : _a.contains("b_msg")) && ((_b = ele.classList) === null || _b === void 0 ? void 0 : _b.contains("b_canvas")))
return true;
// 相关搜索
if (ele.classList.contains("b_ans") && ((_c = ele.querySelector("h2")) === null || _c === void 0 ? void 0 : _c.textContent) == "相关搜索")
return true;
// 广告
if ((_d = ele.classList) === null || _d === void 0 ? void 0 : _d.contains("b_ad"))
return true;
// 下一页
if (ele.classList.contains("b_pag"))
return true;
// 黑名单标题
if (banlistData.has((_e = ele.querySelector("h2")) === null || _e === void 0 ? void 0 : _e.textContent, "global" /* GM_KEY.WORD */))
return true;
// 黑名单链接
if (banlistData.has(parseItemCite(ele), "bingCite" /* GM_KEY.CITE */))
return true;
return false;
}
// 为条目增加可以屏蔽该引用地址的按钮
function addBlockButton(ele) {
let cite = parseItemCite(ele);
if (!cite)
return;
try {
const urlObj = new URL(cite);
cite = urlObj.origin;
}
catch (e) {
console.log("该条目的引用不是有效网址!");
}
const btn = document.createElement("button");
btn.className = "banlist-block-btn";
btn.textContent = "临兵斗者皆阵列在前"[Date.now() % 9];
btn.addEventListener("click", (event) => {
event.preventDefault();
event.stopImmediatePropagation(); // 彻底阻止传播
removeElement(ele);
banlistData.add(cite, "bingCite" /* GM_KEY.CITE */);
});
ele.appendChild(btn);
}
function setNewTabOpen(ele) {
ele.querySelectorAll("a[href]").forEach((a) => {
a.setAttribute("target", "_blank");
});
}
// 清除黑名单条目的方法
function removeElement(ele) {
ele.outerHTML = "<!--has been banned-->";
}
// bing搜索中网站图片采用懒加载,使其正常显示
function transRmsImage(ele) {
// 将div.rms_iac元素改为图片
const rmsImages = ele.querySelectorAll("div.rms_iac[data-src]");
rmsImages.forEach((imgDiv) => {
try {
const img = document.createElement("img");
Object.keys(imgDiv.dataset).forEach((key) => {
// 将 data-src 转换为 src,data-alt 转换为 alt,依此类推
const attrName = key.replace("data-", "");
img.setAttribute(attrName, imgDiv.dataset[key]);
});
imgDiv.replaceWith(img);
}
catch (_a) {
console.log("转换图片失败!");
}
});
}
// 判断第一页搜索结构是否已经显示完
function isFirstPageComplete() {
// (document.readyState === "complete")
return document.querySelector(".sb_fullnpl , .sb_halfnext") != null;
}
// bing默认每页加载10条,但是后5条则采用ajax加载。每页请求5条以内则可以绕过该问题。
const RESULT_PER_LOAD = 4;
// 获得下一页地址,优先采用bing提供的下一页链接,其次采用人为构建
function getNextUrl(curUrl, curDocument) {
var _a;
const urlObj = (_a = _getNextUrlByNextButton(curUrl, curDocument)) !== null && _a !== void 0 ? _a : _getNextUrlBySearchParams(curUrl);
urlObj.searchParams.set("count", `${RESULT_PER_LOAD}`);
return urlObj.href;
}
function _getNextUrlByNextButton(curUrl, curDocument) {
var _a;
let nextUrl = (_a = curDocument.querySelector(".sb_fullnpl , .sb_halfnext")) === null || _a === void 0 ? void 0 : _a.getAttribute("href");
if (!nextUrl)
return null;
const urlObj = new URL(nextUrl, curUrl);
let first = urlObj.searchParams.get("first");
if (first == null)
return null;
return urlObj;
}
function _getNextUrlBySearchParams(curUrl) {
const urlObj = new URL(curUrl);
let first = urlObj.searchParams.get("first");
if (first == null) {
first = "8";
}
else {
first = String(parseInt(first) + RESULT_PER_LOAD);
}
urlObj.searchParams.set("first", first);
return urlObj;
}
function getDocument(targetUrl, requestData = {}) {
return new Promise((resolve, reject) => {
const _requestData = {
method: "GET",
url: targetUrl,
timeout: 5000,
...requestData,
onload: function (response) {
console.log(`获取内容成功:\n${targetUrl}`);
const htmlContent = response.responseText;
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, "text/html");
resolve(doc);
},
onerror: function (error) {
console.log(`获取内容失败:\n${targetUrl}`);
reject(error);
},
ontimeout: function () {
console.log(`获取内容超时:\n${targetUrl}`);
reject();
},
};
GM_xmlhttpRequest(_requestData);
});
}
// **入口函数**
function main() {
autoLoadPage();
GM_registerMenuCommand("添加全局关键词", createConfigWindow);
}
main();
})();