AC-baidu: 优化百度、搜狗、谷歌搜索结果之关键词自动高亮

1.自动提取搜索页面的搜索关键词 2.对关键词自动进行高亮处理 W键可以取消高亮 3.动态获取动态的搜索关键词,重新高亮显示

  1. // ==UserScript==
  2. // @name AC-baidu: 优化百度、搜狗、谷歌搜索结果之关键词自动高亮
  3. // @description 1.自动提取搜索页面的搜索关键词 2.对关键词自动进行高亮处理 W键可以取消高亮 3.动态获取动态的搜索关键词,重新高亮显示
  4. // @icon https://gitee.com/remixAC/GM_script/raw/master/images/head.jpg
  5. // @author AC
  6. // @create 2018-05-25
  7. // @version 26.14
  8. // @include *
  9. // @exclude *://www.bilibili.com/*
  10. // @home-url https://greasyfork.org/zh-TW/scripts/368418
  11. // @home-url2 https://github.com/langren1353/GM_script
  12. // @namespace 1353464539@qq.com
  13. // @copyright 2017, AC
  14. // @lastmodified 2024-03-07
  15. // @feedback-url https://greasyfork.org/zh-TW/scripts/368418
  16. // @note 2024.03.07-V26.14 修复页面的严重卡顿问题,优化原始代码,基本不卡
  17. // @note 2024.03.06-V26.13 增加搜索引擎:DuckDuckGo;增加自动翻页的适配,优化性能问题;function fix
  18. // @note 2022.04.24-V2.3 增加搜索引擎:fsou的支持
  19. // @note 2019.08.09-V2.2 排除bilibili的地址,避免导致bilibili无法播放
  20. // @note 2019.06.05-V2.1 修复样式加载刚开始的时候还是黑色的,颜色没有及时更新的问题 其次优化脚本的处理速速,减少不必要的查询和处理
  21. // @note 2018.10.08-V2.0 修复多次触发导致的卡顿现象;修复搜索时高亮的问题
  22. // @note 2018.10.03-V1.9 修复由于<span>标签导致的:1.样式被界面污染 2.特定关键词被百度重定向脚本删除;修复在部分代码界面导致的高亮失效问题;修复高亮导致的标题栏被格式化
  23. // @note 2018.07.21-V1.8 修复由于很快的高亮导致的高亮代码被个格式化为普通文本
  24. // @note 2018.07.06-V1.7 修复csdn的问题和w3cschool页面的代码问题
  25. // @note 2018.06.20-V1.6 修复上次更新导致的严重bug,页面卡死问题
  26. // @note 2018.06.18-V1.5 修复在部分csdn网页代码上,高亮不起作用
  27. // @note 2018.06.15-V1.4 修复在CSDN代码中,高亮处理之后导致的多了一部分文字的问题;减少了多次调用可能出现的冲突问题;依旧添加G键也是高亮效果
  28. // @note 2018.06.04-V1.3 1.修复百度翻页之后偶尔不自动高亮的问题;2.新增一定的页面高亮效果的调整;优化关键词的数量,数量到达一定程度,之后的关键词不做高亮处理
  29. // @note 2018.05.31-V1.2 修复由于分词不准的问题导致的丢词问题;修复停止高亮然后又自动启动的问题;修复下划线分割问题;修复左右尖括号转换问题;新增W高亮时复制文字内容
  30. // @note 2018.05.26-V1.1 修复垃圾代码,上个版本真的是垃圾代码,运行又慢,占用又高,还特么定时运行,但就是数据出现很慢,现在应该没有这个问题了
  31. // @note 2018.05.25-V1.0 从百度重定向脚本中拆分出来
  32. // @run-at document-body
  33. // @grant GM_getValue
  34. // @grant GM_setValue
  35. // @grant GM_setClipboard
  36. // ==/UserScript==
  37.  
  38. const isDebug = true; // 本次更新是否有新功能需要展示
  39. const debugX = isDebug ? console.error.bind(console) : function() {};
  40.  
  41. (function () {
  42. 'use strict';
  43. const startTime = new Date().getTime();
  44. const renderStartTime = 3000; // 5秒钟 & 强制刷新应该优先于定时操作
  45. const HightLightColorList = ["#FFFF80", "#90EE90", "#33FFFF", "#FF6600", "#FF69B4", "#20B2AA", "#8470FF"];
  46. let OnlyDBCheck = false; // 是否为双击事件
  47. let dataConflictLock = false;
  48. let highLight_timer = null
  49. const keySets = {}
  50. let SiteTypeID; // 标记当前是哪个站点[百度=1;搜狗=2;3=好搜;谷歌=4;必应=5;知乎=6;其他=7]
  51. const SiteType = {
  52. BAIDU: 1,
  53. SOGOU: 2,
  54. SO: 3,
  55. GOOGLE: 4,
  56. BING: 5,
  57. ZHIHU: 6,
  58. OTHERS: 7,
  59. FSOU: 8,
  60. DUCK: 9,
  61. };
  62. /*在搜索引擎上面会刷新当前搜索关键词内容*/
  63. if (location.host.indexOf("www.baidu.com") > -1) {
  64. SiteTypeID = SiteType.BAIDU;
  65. } else if (location.host.indexOf("sogou") > -1) {
  66. SiteTypeID = SiteType.SOGOU;
  67. } else if (location.host.indexOf("so.com") > -1) {
  68. SiteTypeID = SiteType.SO;
  69. } else if (location.host.indexOf("google") > -1) {
  70. SiteTypeID = SiteType.GOOGLE;
  71. } else if (location.host.indexOf("bing") > -1) {
  72. SiteTypeID = SiteType.BING;
  73. } else if (location.host.indexOf("fsoufsou") > -1) {
  74. SiteTypeID = SiteType.FSOU;
  75. } else if (location.host.indexOf("zhihu.com") > -1) {
  76. SiteTypeID = SiteType.ZHIHU;
  77. } else if (location.host.indexOf("duckduckgo.com") > -1) {
  78. SiteTypeID = SiteType.DUCK;
  79. } else {
  80. SiteTypeID = SiteType.OTHERS; // 非OTHER的都会被设置关键词,并准备高亮
  81. }
  82. function AC_addStyle(css, className, addToTarget, isReload){ // 添加CSS代码,不考虑文本载入时间,带有className
  83. const tout = setInterval(function() {
  84. if (addToTarget == null) addToTarget = "head";
  85. if (isReload == null) isReload = false;
  86. if (document.querySelector(addToTarget) != null) {
  87. clearInterval(tout);
  88. const checkNode = document.querySelector("." + className) || null;
  89. if (isReload === false && checkNode != null) {
  90. // 节点存在,并且不准备覆盖
  91. return;
  92. }
  93. if (checkNode != null && css === checkNode.innerHTML) {
  94. // checkNode可访问 & 内容相同 == > else 没有节点 || 内容不同
  95. return;
  96. }
  97. safeRemove("." + className);
  98. const cssNode = document.createElement("style");
  99. if (className != null)
  100. cssNode.className = className;
  101. cssNode.innerHTML = css;
  102. try {
  103. document.querySelector(addToTarget).appendChild(cssNode);
  104. } catch (e) {
  105. debugX(e.message);
  106. }
  107. }
  108. }, 50);
  109. }
  110. function safeRemove(cssSelector){
  111. try {
  112. document.querySelector(cssSelector).remove();
  113. }catch (e){}
  114. }
  115. function DoHighLightCheck(e) { // 手动W触发
  116. const target = e.target;
  117. const selectedText = getSelectedText(target);
  118. const s_keyup = (e.type === 'keydown') && (['G', 'W'].find(one => one.charCodeAt(0) === e.keyCode)); // 是按下特殊按键
  119. if (s_keyup) {
  120. if(typeof(selectedText) == "undefined" || selectedText == null || selectedText === ""){
  121. debugX("不准亮");
  122. GM_setValue("searchKeyWords", ""); // 置空
  123. safeRemove(".AC-highLightRule");
  124. unHighLightAll_Text();
  125. } else {
  126. OnlyDBCheck = true;
  127. doHighLightTextS(selectedText, true);
  128. }
  129. }
  130. }
  131. function doHighLightTextS(selectedText, dbclick = false) {
  132. if (!selectedText) return
  133. unHighLightAll_Text();
  134. if(dbclick){
  135. GM_setValue("searchKeyWords", selectedText);
  136. debugX('原始:', keySets.keywords, '激活:', selectedText);
  137. }
  138. initKeySets(selectedText);
  139. doHighLightAll_CSS();
  140. doHighLightAll_Text();
  141. highLight_timer && clearInterval(highLight_timer)
  142. // 增加一个定时刷新关键词的函数,保证页面翻页的时候也有效
  143. highLight_timer = setInterval(function(){
  144. if(!document.hidden) doHighLightAll_Text();
  145. }, 5000);
  146. }
  147. function getSelectedText(target) {
  148. function getTextSelection() {
  149. let selectedText = '';
  150. if (target.hasAttribute("type")) {
  151. if (target.getAttribute("type").toLowerCase() === "checkbox") return '';
  152. }
  153. const value = target.value;
  154. if (value) {
  155. const startPos = target.selectionStart;
  156. const endPos = target.selectionEnd;
  157. if (!isNaN(startPos) && !isNaN(endPos)) selectedText = value.substring(startPos, endPos);
  158. return selectedText;
  159. } else return '';
  160. }
  161. let selectedText = window.getSelection().toString();
  162. if (!selectedText) selectedText = getTextSelection();
  163. return selectedText;
  164. }
  165. function getBLen(str) {
  166. if (str == null) return 0;
  167. if (typeof str != "string"){
  168. str += "";
  169. }
  170. return str.replace(/[^\x00-\xff]/g,"01").length;
  171. }
  172. function reSplitKeySet(keySet){
  173. const data = keySet
  174. .split(/\b |[\u0000-\u002F\u003A-\u0040\u005B-\u005e\u007B-\u00FF\uFF00-\uFFFF\u3000-\u303F]/g)
  175. .join('ACsCA')
  176. .replace(/[^\u4E00-\u9FA5|0-9a-zA-Z_]+/g, "")
  177. .replace(/(ACsCA){2}/g, "ACsCA")
  178. .replace(/(^ACsCA|ACsCA$)/g, "")
  179. .split("ACsCA");
  180. return data.filter(one => one.length >= 2) // 长度太短的不要
  181. }
  182. // 初始化点击的文字信息
  183. function initKeySets(selection){
  184. // 1.split通过特殊字符和字符边界分割串[非[0-9A-Za-z]特殊字符]
  185. // 2.通过特定字符连接原始串,
  186. // 3.1移除多次重复的特定串,非常用串移除,避免空串
  187. // 3.2移除开头或者结尾的特定串,避免分割后出现空白数据,
  188. // 4.按特定串分割
  189. keySets.keywords = reSplitKeySet(selection);
  190. keySets.length = keySets.keywords.length;
  191. }
  192. function getTextColor(textIndex) {
  193. return HightLightColorList[textIndex % HightLightColorList.length]
  194. }
  195. function doHighLightAll_CSS(){
  196. const selection = GM_getValue("searchKeyWords", "");
  197. // debugX("执行高亮"+selection);
  198. keySets.keywords = reSplitKeySet(selection);
  199. if(keySets.length) {
  200. const baseRule = ".acWHSet{display:inline!important;box-shadow: -3px 0px 3px 0.15px rgba(0, 0, 0, 0.15); color: rgb(0,0,0); font-weight:inherit;}";
  201. const otherRule = keySets.keywords.map((keyword, index) => {
  202. return `.acWHSet[data='${keyword.toLocaleLowerCase()}']{background-color:${getTextColor(index)};}`;
  203. }).join('\n')
  204. AC_addStyle(baseRule + otherRule, "AC-highLightRule", "body", true);
  205. }
  206. }
  207. function doHighLightAll_Text(){
  208. if(dataConflictLock === true) return;
  209. dataConflictLock = true;
  210. doHighLightAll_Text_Inner();
  211. dataConflictLock = false;
  212. }
  213. // BUG- 卡顿严重,注意时间消耗
  214. function doHighLightAll_Text_Inner(){
  215. if(keySets.keywords.length === 0 || keySets.keywords > 20) {
  216. return;
  217. }
  218. const XhighLight = document.createElement('XhighLight');
  219. const evalRule = './/text()[normalize-space() != "" ' +
  220. 'and not(parent::XhighLight[@txhidy15]) ' +
  221. 'and not(parent::title)' +
  222. 'and not(ancestor::style) ' +
  223. 'and not(ancestor::script) ' +
  224. 'and not(ancestor::textarea) ' +
  225. 'and not(ancestor::div[@id="thdtopbar"]) ' +
  226. 'and not(ancestor::div[@id="kwhiedit"]) ' +
  227. 'and not(parent::XhighLight[@txhidy15]) ' +
  228. 'and not(ancestor::pre) ' + // CSDN的代码文字,未初始化之前的1--->不作处理
  229. ((new Date().getTime() - startTime > renderStartTime || OnlyDBCheck === true) ?
  230. ('or ((ancestor::pre) and not(parent::XhighLight[@txhidy15]) and (' + // EG.http://www.w3school.com.cn/xpath/xpath_syntax.asp
  231. '(ancestor::code[@class]) ' + // EG.http://lib.csdn.net/article/android/8894
  232. 'or (ancestor::div[contains(@class, "cnblogs_code")] ) ' + // EG.https://blog.csdn.net/freeape/article/details/50485067
  233. "))") : "") +
  234. ']';
  235. const snapElements = document.evaluate(evalRule, document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  236. if (!snapElements.snapshotItem(0)) {
  237. return;
  238. } // 退出2
  239. try{
  240. const pat = new RegExp("(" + keySets.keywords.join("|") + ")", "gi")
  241. for (let i = 0, len = snapElements.snapshotLength; i < len; i++) {
  242. const node = snapElements.snapshotItem(i);
  243. if (node.nodeValue.length > 1 && pat.test(node.nodeValue)) {
  244. if(node.className!= null && node.className.indexOf("acWHSet") > 0) return;
  245. const sp = XhighLight.cloneNode(true);
  246. const findResult = node.nodeValue.replace(/[<>"&]/g, function(match) {
  247. switch (match) {
  248. case "<":
  249. return "&lt;";
  250. case ">":
  251. return "&gt;";
  252. case "&":
  253. return "&amp;";
  254. case "\"":
  255. return "&quot;";
  256. }
  257. });
  258. sp.innerHTML = findResult.replace(pat, function(matchT, _$1) {
  259. const lowerData = _$1.toLocaleLowerCase();
  260. return '<XhighLight class="THmo acWHSet" data="' + lowerData + '" txhidy15="acWHSet">' + _$1 + '</XhighLight>';
  261. });
  262. if(!node.parentNode) continue;
  263. node.parentNode.replaceChild(sp, node);
  264. sp.outerHTML = sp.innerHTML;
  265. }
  266. }
  267. }catch (e) {
  268. debugX(e);
  269. }
  270. }
  271. function unHighLightAll_Text(){
  272. try{
  273. const tgts = document.querySelectorAll('XhighLight[txhidy15="acWHSet"]');
  274. for (let i = 0; i<tgts.length; i++){
  275. let parnode = tgts[i].parentNode, parpar = parnode.parentNode, tgtspan;
  276. if (parnode.hasAttribute("thdcontain") && parnode.innerHTML === tgts[i].outerHTML){
  277. parnode.outerHTML = tgts[i].textContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  278. tgtspan = parpar;
  279. } else {
  280. tgts[i].outerHTML = tgts[i].textContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  281. tgtspan = parnode;
  282. }
  283. tgtspan.normalize();
  284. if (tgtspan.hasAttribute("thdcontain")){
  285. parnode = tgtspan.parentNode;
  286. if (parnode){
  287. if (parnode.hasAttribute("thdcontain") && parnode.innerHTML === tgtspan.outerHTML && tgtspan.querySelectorAll('XhighLight[txhidy15]').length === 0){
  288. parnode.outerHTML = tgtspan.innerHTML;
  289. } else if (parnode.innerHTML === tgtspan.outerHTML && tgtspan.querySelectorAll('XhighLight[txhidy15]').length === 0) {
  290. parnode.innerHTML = tgtspan.innerHTML;
  291. }
  292. }
  293. }
  294. }
  295. const oldTgs = document.querySelectorAll("XhighLight[thdcontain='true']");
  296. for(let i = 0; i < oldTgs.length; i++){
  297. const curTg = oldTgs[i];
  298. markChildandRemove(curTg);
  299. }
  300. }catch (e){}
  301. }
  302. function markChildandRemove(node){
  303. try{
  304. if(node.tagName.toLowerCase() === "xhighlight"){
  305. node.outerHTML = node.innerHTML;
  306. }
  307. const childList = node.childNodes;
  308. for(let i=0; i < childList.length; i++){
  309. markChildandRemove(childList[i]);
  310. if(node.tagName.toLowerCase() === "xhighlight"){
  311. node.outerHTML = node.innerHTML;
  312. }
  313. }
  314. }catch (e){}
  315. }
  316.  
  317. let nowHighLightText = ''
  318. // 如果是搜索引擎的话
  319. if(SiteTypeID !== SiteType.OTHERS){ // 启用自动高亮
  320. // 持续拿到搜索关键词,存入GM中,避免切换页面导致的关键词丢失
  321. setInterval(() => {
  322. const searchKW = [...(window.location.search.substr(1) + "").split("&")].find(one => /^(wd|q|query)=.*$/.test(one))
  323. const searchValue = decodeURI(searchKW ? searchKW.split("=")[1] : "")
  324. if(nowHighLightText !== searchValue) {
  325. nowHighLightText = searchValue
  326. GM_setValue("searchKeyWords", searchValue)
  327. doHighLightTextS(searchValue)
  328. }
  329. }, 1000);
  330. } else {
  331. nowHighLightText = GM_getValue("searchKeyWords", "")
  332. doHighLightTextS(nowHighLightText)
  333. }
  334. setTimeout(function(){
  335. // 似乎过早的绑定可能出现问题,例如www.huomao.com中h5视频的LOGO一直在
  336. document.addEventListener('keydown', DoHighLightCheck, true);
  337. }, 800);
  338. })();