// ==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 = ""; } // 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(); })();