import { addStyle, DOMUtils, log, pops, utils } from "@/env"; import { Panel } from "@components/setting/panel"; import { PanelUISize } from "@components/setting/panel-ui-size"; import Qmsg from "qmsg"; import { BaiduHandleResultItem } from "./SearchHandleResultItem"; type BaiDuSearchRuleMode = "match-href" | "match-attr" | "contains-child" | "remove-child"; interface BaiDuSearchRuleDetail { mode: BaiDuSearchRuleMode; matchText?: string | RegExp; attr?: string; } interface BaiduSearchRuleConfig extends BaiDuSearchRuleDetail { moreRule?: BaiDuSearchRuleDetail[]; } // * 百度搜索自定义拦截规则 export const BaiduSearchBlockRule = { defaultRule: ` // 百度健康 // match-href##expert.baidu.com match-attr##srcid##med_wz // 百度健康病案库 match-attr##srcid##med_medical_records_san // 百度健康卖药的 match-attr##srcid##med_disease_drug // 大家还在搜 match-href##recommend_list.baidu.com&&&&match-attr##tpl##recommend_list // 大家还在搜:隐藏的(点击后,跳出来的) remove-child##.c-atom-afterclick-recomm-wrap // 百家号聚合 match-href##author.baidu.com/home/ // xxx 相关 xxx match-attr##srcid##(sigma|vid_fourfold) // 问一问 match-attr##data-log##wenda_inquiry // 百度问一问 match-attr##srcid##wenda_edu // 百度游戏 match-attr##srcid##yx_entity_san // 大家还在看 match-attr##srcid##yl_recommend_list // 百度-智能小程序 match-attr##srcid##xcx_multi // 百度 xx精选商品问答 match-attr##srcid##b2b_wenda_wise // 百度爱采购 match-attr##srcid##b2b_straight_wise_vertical // ↓会误杀有些情况下是百度知道的回答链接 // match-attr##srcid##lego_tpl match-href##^http(s|)://b2b.baidu.com // 百度优选 match-attr##srcid##sp_purc_san // 全网热卖 match-attr##srcid##sp_purc_atom // 百度本地生活 match-attr##srcid##jy_bdb_in_store_service_2nd // AI智能体推广 match-attr##srcid##ai_agent_distribute // AI智能体 智能回复 match-attr##srcid##ai_agent_qa_recommend // AI智能体 超多相关角色-等你来聊 match-attr##srcid##yl_actor_agent // 百度游戏中心 match-attr##srcid##yx_entity_pc_san // 百度健康 match-attr##srcid##med_wenzhen_san // 搜索聚合 // match-attr##srcid##note_lead // 资讯 // match-attr##srcid##realtime // 百度有驾 // match-attr##srcid##(car_kg2_san|car_view_point_san) // 动态(微博、百度动态...等) // match-attr##srcid##rel_ugc_san `, /** * 搜索规则 */ rule: [] as BaiduSearchRuleConfig[], init() { this.initRule(); Panel.execMenuOnce("baidu-search-add-filter-button", () => { const $css = addStyle(/*css*/ ` .gm-search-filter-wrapper{ display: flex; align-items: center; justify-content: flex-end; } .sc-feedback:has(.gm-search-filter-wrapper){ justify-content: center; gap: 6px; } `); const lockFn = new utils.LockFunction(() => { BaiduHandleResultItem.$el.$resultList.forEach(($result) => { const url = BaiduHandleResultItem.getSearchArticleOriginal_link($result); if (Panel.getValue("baidu-search-filter-enable") && BaiduSearchBlockRule.checkFilter($result, url)) { log.info("触发自定义规则,屏蔽该搜索结果,url:", url); $result.remove(); return; } // 添加过滤按钮 BaiduSearchBlockRule.addFilterButton($result); }); }); const observer = utils.mutationObserver(document, { config: { subtree: true, childList: true, }, immediate: true, callback: () => { lockFn.run(); }, }); return [ $css, () => { observer.disconnect(); }, () => { DOMUtils.remove(".gm-search-filter-wrapper"); }, ]; }); }, // * 获取本地存储的自定义拦截规则 getLocalRule() { let localRule = Panel.getValue("baidu-search-interception-rules", ""); localRule = localRule.trim(); return localRule; }, // * 设置本地存储的自定义拦截规则 setLocalRule(rule: string) { Panel.setValue("baidu-search-interception-rules", rule); }, // * 清空规则 clearLocalRule() { Panel.deleteValue("baidu-search-interception-rules"); }, /** * 追加规则 */ addRule(rule: string) { let localRule = this.getLocalRule(); localRule += "\n" + rule.trim(); this.setLocalRule(localRule); }, /** * 初始化转换后的规则 */ initRule() { let localRule = this.getLocalRule(); if (Panel.getValue("baidu-search-blockNoteLead")) { this.defaultRule += "\n" + "match-attr##srcid##note_lead"; } if (Panel.getValue("baidu-search-enable-default-interception-rules")) { localRule = this.defaultRule + "\n\n" + localRule; } if (Array.isArray(this.rule) && this.rule.length) { this.rule.length = 0; } else { this.rule = []; } this.rule = this.parseRule(localRule); }, /** * 把规则进行转换 * @param localRule */ parseRule(localRule: string) { let result: BaiduSearchRuleConfig[] = []; function parseOneRule(ruleItem: string) { let cRuleItemSplit = ruleItem.split("##"); if (!cRuleItemSplit.length) { log.error("无效规则", ruleItem); return; } let ruleName = cRuleItemSplit[0]; let ruleNameLowerCase = ruleName.toLowerCase(); let endRule = ruleItem.replace(ruleName + "##", ""); if (ruleNameLowerCase === "match-href") { return { rule: ruleItem, mode: ruleNameLowerCase as BaiDuSearchRuleMode, matchText: new RegExp(endRule), }; } else if (ruleNameLowerCase === "match-attr") { let otherRuleSplit = endRule.split("##"); if (otherRuleSplit.length === 1) { log.error("无效规则", ruleItem); return; } let attrName = otherRuleSplit[0]; let attrValueMatch = endRule.replace(attrName + "##", ""); return { rule: ruleItem, mode: ruleNameLowerCase as BaiDuSearchRuleMode, attr: attrName, matchText: new RegExp(attrValueMatch), }; } else if (ruleNameLowerCase === "contains-child" || ruleNameLowerCase === "remove-child") { return { rule: ruleItem, mode: ruleNameLowerCase as BaiDuSearchRuleMode, matchText: endRule, }; } else { log.error("无效规则", ruleItem); } } localRule.split("\n").forEach((ruleItem) => { ruleItem = ruleItem.trim(); if (ruleItem === "") { return; } if (ruleItem.startsWith("//")) { return; } let moreRule = ruleItem.split("&&&&"); if (moreRule.length === 1) { let parsedRule = parseOneRule(ruleItem); if (parsedRule) { result.push(parsedRule); } } else { let resultRule: BaiduSearchRuleConfig[] = []; moreRule.forEach((oneRule) => { oneRule = oneRule.trim(); let parsedRule = parseOneRule(oneRule); if (parsedRule) { resultRule.push(parsedRule); } }); result.push({ mode: "more-rule" as BaiDuSearchRuleMode, moreRule: resultRule, }); } }); return result; }, /** * 执行自定义规则,拦截返回true * @param $el * @param url 真实链接 */ checkFilter($el: HTMLElement, url?: string) { function handleOneRule(ruleItem: BaiduSearchRuleConfig) { if (ruleItem.mode === "match-href") { if (typeof url === "string" && url.match(ruleItem.matchText as RegExp)) { return true; } } else if (ruleItem.mode === "match-attr") { if ( $el.hasAttribute(ruleItem.attr as string) && $el.getAttribute(ruleItem.attr as string)?.match(ruleItem.matchText as RegExp) ) { return true; } } else if (ruleItem.mode === "contains-child") { if ($el.querySelector(ruleItem.matchText as string)) { return true; } } else if (ruleItem.mode === "remove-child") { $el.querySelector(ruleItem["matchText"] as string)?.remove(); } } for (const ruleItem of this.rule) { if (ruleItem.moreRule) { for (const oneRule of ruleItem.moreRule) { if (handleOneRule(oneRule)) { if (import.meta.hot) { console.log("自定义规则:", ruleItem); } return true; } } } else { if (handleOneRule(ruleItem)) { if (import.meta.hot) { console.log("自定义规则:", ruleItem); } return true; } } } }, /** * 创建过滤按钮 * @param $searchResult 每一个搜索项 */ createFilterButton() { const $btn = DOMUtils.createElement("div", { className: "gm-search-filter-wrapper", innerHTML: /*html*/ ` `, }); return $btn; }, /** * 新增过滤按钮 * @param $searchResult 每一个搜索项 */ addFilterButton($searchResult: HTMLElement) { const classNameAttr = "data-filter-btn"; if ($searchResult.hasAttribute(classNameAttr)) { return; } $searchResult.setAttribute(classNameAttr, "true"); const $filterBtn = this.createFilterButton(); DOMUtils.on( $filterBtn, "click", (evt) => { DOMUtils.preventEvent(evt); const url = BaiduHandleResultItem.getSearchArticleOriginal_link($searchResult); const ruleList: string[] = []; const srcid = $searchResult.getAttribute("srcid"); let rule_attr_srcid, rule_attr_new_srcid, rule_attr_tpl, rule_href_hostname, rule_href_hostname_pathname; if (utils.isNotNull(srcid)) { rule_attr_srcid = `match-attr##srcid##${srcid}`; ruleList.push(rule_attr_srcid); } const new_srcid = $searchResult.getAttribute("new_srcid"); if (utils.isNotNull(new_srcid)) { rule_attr_new_srcid = `match-attr##new_srcid##${new_srcid}`; ruleList.push(rule_attr_new_srcid); } const tpl = $searchResult.getAttribute("tpl"); if (utils.isNotNull(tpl)) { rule_attr_tpl = `match-attr##tpl##${tpl}`; ruleList.push(rule_attr_tpl); } if (utils.isNotNull(url)) { try { const urlInst = new URL(url); rule_href_hostname = `match-href##${urlInst.hostname}`; rule_href_hostname_pathname = `match-href##${urlInst.hostname}${urlInst.pathname}`; ruleList.push(rule_href_hostname); ruleList.push(rule_href_hostname_pathname); if (utils.isNotNull(srcid)) { ruleList.push([rule_attr_srcid, rule_href_hostname].join("&&&&")); } if (utils.isNotNull(new_srcid)) { ruleList.push([rule_attr_new_srcid, rule_href_hostname].join("&&&&")); } if (utils.isNotNull(tpl)) { ruleList.push([rule_attr_tpl, rule_href_hostname].join("&&&&")); } } catch { ruleList.push(`match-href##url##${url}`); } } if (!ruleList.length) { Qmsg.error("生成过滤规则失败"); return; } const $dialog = pops.confirm({ title: { text: "快捷添加过滤规则", position: "center", }, content: { text: /*html*/ `
${ruleList .map((it, index) => { return /*html*/ `
${it}
`; }) .join("\n")}
`, html: true, }, btn: { ok: { enable: false, }, cancel: { enable: false, }, }, mask: { clickEvent: { toClose: true, }, }, width: PanelUISize.setting.width, height: "auto", style: /*css*/ ` .quick-add-rule-wrapper{ display: flex; flex-direction: column; align-items: flex-start; gap: 15px; padding: 10px; } .quick-add-rule-item{ cursor: pointer; } .quick-add-rule-item code { display: flex; flex-wrap: nowrap; } `, }); DOMUtils.on( $dialog.$pops, "click", ".quick-add-rule-item", (evt2, $ruleItem) => { DOMUtils.preventEvent(evt2); const index = Number($ruleItem.getAttribute("data-list-index")!); const rule = ruleList[index]; if (rule == null) { log.error(rule, index); Qmsg.error("规则不存在"); return; } log.info("添加过滤规则:", rule); Qmsg.success("添加成功"); this.addRule(rule); this.initRule(); BaiduHandleResultItem.removeAds(); $dialog.close(); }, { capture: true, overrideTarget: false, } ); }, { capture: true, overrideTarget: false, } ); const setButtonRuleMap: (() => string | void)[] = [ () => { if ($searchResult.getAttribute("srcid") !== "new_baikan_index") return; // AI总结 // 百度AI 全网总结xx篇结果 // 动态的,等待它加载 DOMUtils.waitNode( '[data-show="interaction"] [class^="interact-container"]', $searchResult, 10000 ).then(($interaction) => { if (!$interaction) return; // 添加该按钮又会导致无法展开 // 覆盖点击事件 const $foldSwitch = $searchResult.querySelector(".cos-fold-switch"); if ($foldSwitch) { DOMUtils.on( $foldSwitch, "click", (evt) => { DOMUtils.preventEvent(evt); const $baikan = $searchResult.querySelector("#baikan-content"); if (!$baikan) { Qmsg.error("未找到#baikan-content"); return; } DOMUtils.css($baikan, "max-height", ""); DOMUtils.remove($foldSwitch); DOMUtils.remove($searchResult.querySelectorAll('[class^="cosd-fold-switch"]')); }, { capture: true } ); } else { log.error("未找到【展开】按钮,无法覆盖点击事件,某些浏览器上会无法进行展开"); } DOMUtils.append($interaction, $filterBtn); }); return "interaction"; }, () => { let $feedback = $searchResult.querySelector(".cosc-feedback") || $searchResult.querySelector(".sc-feedback") || $searchResult.querySelector("div:has(>.c-gap-left-middle)"); if (!$feedback) return; const srcid = $searchResult.getAttribute("srcid") || ""; if (["poi_map", "lego_tpl"].includes(srcid)) { // 百度地图 // 爱企查 const $leftMiddle = $feedback.querySelector(".c-gap-left-middle")!; if (srcid === "lego_tpl") { $feedback = $leftMiddle; } const lockFn = new utils.LockFunction(() => { if ($searchResult.contains($filterBtn)) return; if (DOMUtils.prev($feedback!) === $filterBtn) return; DOMUtils.before($feedback!, $filterBtn); }); utils.mutationObserver($searchResult, { config: { subtree: true, childList: true, attributes: true, }, immediate: true, callback: () => { lockFn.run(); }, }); } else if ($feedback) { const feedbackRect = $feedback.getBoundingClientRect(); if (!feedbackRect.width || !feedbackRect.height) return; // 右下角反馈按钮里面(必须是显示的) DOMUtils.prepend($feedback, $filterBtn); } return "feedback"; }, () => { const $cardSection = $searchResult.querySelector("article.cosc-card section"); if (!$cardSection) return; DOMUtils.append($cardSection, $filterBtn); return "cardSection"; }, () => { const $section = $searchResult.querySelector("article section"); if (!$section) return; DOMUtils.append($section, $filterBtn); return "section"; }, ]; let attrValue = ""; for (let index = 0; index < setButtonRuleMap.length; index++) { const item = setButtonRuleMap[index]; const result = item(); if (typeof result === "string") { attrValue = result; break; } } if (import.meta.env.DEV && attrValue === "") { log.error("未找到用于添加过滤按钮的父元素,无法新增过滤按钮", $searchResult); } $filterBtn.setAttribute("data-filter-type", attrValue); $searchResult.setAttribute(classNameAttr, attrValue); }, };