remove the jump link in BAIDU (ECMA6)

去除百度搜索跳转链接

当前为 2016-04-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name remove the jump link in BAIDU (ECMA6)
  3. // @author axetroy
  4. // @description 去除百度搜索跳转链接
  5. // @version 2016.4.10
  6. // @grant GM_xmlhttpRequest
  7. // @include *www.baidu.com*
  8. // @connect tags
  9. // @connect *
  10. // @compatible chrome 完美运行
  11. // @compatible firefox 完美运行
  12. // @supportURL http://www.burningall.com
  13. // @run-at document-start
  14. // @contributionURL troy450409405@gmail.com|alipay.com
  15. // @namespace https://greasyfork.org/zh-CN/users/3400-axetroy
  16. // @license The MIT License (MIT); http://opensource.org/licenses/MIT
  17. // ==/UserScript==
  18.  
  19.  
  20. if (typeof require !== 'undefined' && typeof require === 'function') {
  21. require("babel-polyfill");
  22. }
  23.  
  24. /* jshint ignore:start */
  25. ;(function (window, document) {
  26.  
  27. 'use strict';
  28.  
  29. var ES6Support = true;
  30.  
  31. try {
  32. let test_let = true;
  33. const test_const = true;
  34. var test_tpl_str = `233`;
  35. var test_arrow_fn = (a = '233') => {
  36. };
  37. var test_promise = new Promise(function (resolve, reject) {
  38. resolve();
  39. });
  40. class test_class {
  41.  
  42. }
  43. } catch (e) {
  44. /**
  45. * 促进大家升级浏览器,拯救前端,就是拯救我自己
  46. */
  47. alert('你的浏览器不支持ECMA6,去除百度搜索跳转链接将失效,请升级浏览器和脚本管理器');
  48. ES6Support = false;
  49. }
  50.  
  51. if (!ES6Support) return;
  52.  
  53. let noop = () => {
  54. };
  55.  
  56. /**
  57. * a lite jquery mock
  58. */
  59. class jqLite {
  60. constructor(selectors = '', context = document) {
  61. this.selectors = selectors;
  62. this.context = context;
  63. this.length = 0;
  64.  
  65. switch (typeof selectors) {
  66. case 'undefined':
  67. break;
  68. case 'string':
  69. Array.from(context.querySelectorAll(selectors), (ele, i) => {
  70. this[i] = ele;
  71. this.length++;
  72. }, this);
  73. break;
  74. case 'object':
  75. if (selectors.length) {
  76. Array.from(selectors, (ele, i) => {
  77. this[i] = ele;
  78. this.length++;
  79. }, this);
  80. } else {
  81. this[0] = selectors;
  82. this.length = 1;
  83. }
  84. break;
  85. case 'function':
  86. this.ready(selectors);
  87. break;
  88. default:
  89.  
  90. }
  91.  
  92. };
  93.  
  94. each(fn = noop) {
  95. for (let i = 0; i < this.length; i++) {
  96. fn.call(this, this[i], i);
  97. }
  98. return this;
  99. };
  100.  
  101. bind(types = '', fn = noop) {
  102. this.each((ele)=> {
  103. types.trim().split(/\s{1,}/).forEach((type)=> {
  104. ele.addEventListener(type, (e) => {
  105. let target = e.target || e.srcElement;
  106. if (fn.call(target, e) === false) {
  107. e.returnValue = true;
  108. e.cancelBubble = true;
  109. e.preventDefault && e.preventDefault();
  110. e.stopPropagation && e.stopPropagation();
  111. return false;
  112. }
  113. }, false);
  114. });
  115. });
  116. };
  117.  
  118. ready(fn = noop) {
  119. this.context.addEventListener('DOMContentLoaded', e => {
  120. fn.call(this);
  121. }, false);
  122. }
  123.  
  124. observe(fn = noop, config = {childList: true, subtree: true}) {
  125. this.each((ele) => {
  126. let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  127. let observer = new MutationObserver((mutations) => {
  128. mutations.forEach((mutation) => {
  129. fn.call(this, mutation.target, mutation.addedNodes, mutation.removedNodes);
  130. });
  131. });
  132. observer.observe(ele, config);
  133. });
  134. return this;
  135. };
  136.  
  137. attr(attr, value) {
  138. // one agm
  139. if (!arguments.length === 1) {
  140. // get attr value
  141. if (typeof attr === 'string') {
  142. return this[0].getAttribute(attr);
  143. }
  144. // set attr with a json
  145. else if (typeof attr === 'object') {
  146. this.each(function (ele) {
  147. for (let at in attr) {
  148. if (attr.hasOwnProperty(at)) {
  149. ele.setAttribute(at, value);
  150. }
  151. }
  152. });
  153. }
  154. return value;
  155. }
  156. // set
  157. else if (arguments.length === 2) {
  158. this.each(function (ele) {
  159. ele.setAttribute(attr, value);
  160. });
  161. return this;
  162. }
  163. else {
  164. return this;
  165. }
  166. };
  167.  
  168. removeAttr(attr) {
  169. if (arguments.length === 1) {
  170. this.each((ele)=> {
  171. ele.removeAttribute(attr);
  172. });
  173. }
  174. return this;
  175. }
  176.  
  177. get text() {
  178. let ele = this[0];
  179. return ele.innerText ? ele.innerText : ele.textContent;
  180. };
  181.  
  182. static get fn() {
  183. let visible = (ele)=> {
  184. let pos = ele.getBoundingClientRect();
  185. let w;
  186. let h;
  187. let inViewPort;
  188. let docEle = document.documentElement;
  189. let docBody = document.body;
  190. if (docEle.getBoundingClientRect) {
  191. w = docEle.clientWidth || docBody.clientWidth;
  192. h = docEle.clientHeight || docBody.clientHeight;
  193. inViewPort = pos.top > h || pos.bottom < 0 || pos.left > w || pos.right < 0;
  194. return inViewPort ? false : true;
  195. }
  196. };
  197. let debounce = (fn, delay)=> {
  198. let timer;
  199. return function () {
  200. let agm = arguments;
  201. window.clearTimeout(timer);
  202. timer = window.setTimeout(()=> {
  203. fn.apply(this, agm);
  204. }, delay);
  205. }
  206. };
  207. return {
  208. visible,
  209. debounce
  210. }
  211. };
  212.  
  213. }
  214.  
  215. let $ = (selectors = '', context = document) => {
  216. return new jqLite(selectors, context);
  217. };
  218.  
  219. /**
  220. * cache the ajax response result
  221. * @type {{}}
  222. */
  223. let $$cache = {};
  224.  
  225. /**
  226. * timeout wrapper
  227. * @param fn
  228. * @param delay
  229. * @returns {number}
  230. */
  231. let $timeout = (fn = noop, delay = 0) => {
  232. return window.setTimeout(fn, delay);
  233. };
  234.  
  235. /**
  236. * cancel timer
  237. * @param timerId
  238. * @returns {*}
  239. */
  240. $timeout.cancel = function (timerId) {
  241. window.clearTimeout(timerId);
  242. return timerId;
  243. };
  244.  
  245. let $$count = 0;
  246.  
  247. /**
  248. * ajax function
  249. * @param url the url request
  250. * @param aEle the A link element [non essential variables]
  251. * @returns {Promise}
  252. */
  253. let $ajax = (url, aEle) => {
  254. var deferred = $q.defer();
  255.  
  256. // if in BAIDU home page
  257. if (new RegExp(`${window.location.host}\/?$`, 'im').test(url)) {
  258. $timeout(function () {
  259. deferred.resolve({aEle, url, response: ''});
  260. return deferred.promise;
  261. });
  262. }
  263.  
  264. // if has cache
  265. if ($$cache[url]) {
  266. $timeout(function () {
  267. deferred.resolve({aEle, url, response: $$cache[url]});
  268. return deferred.promise;
  269. });
  270. }
  271.  
  272. // not match the url
  273. if (!/w{3}\.baidu\.com\/link\?url=/im.test(url) && !/w{3}\.baidu\.com\/s/.test(url)) {
  274. $timeout(function () {
  275. deferred.resolve({aEle, url, response: {finalUrl: url}});
  276. return deferred.promise;
  277. });
  278. }
  279.  
  280. // make the protocol agree
  281. if (!new RegExp(`^${window.location.protocol}`).test(url)) {
  282. url = url.replace(/^(http|https):/im, window.location.protocol);
  283. }
  284.  
  285. if (config.debug) console.info(`${$$count++}-ajax:${url}`);
  286.  
  287. if (aEle) $(aEle).attr('decoding', '');
  288.  
  289. GM_xmlhttpRequest({
  290. method: "GET",
  291. url: url,
  292. timeout: 5000,
  293. anonymous: !!aEle,
  294. onreadystatechange: function (response) {
  295. if (response.readyState !== 4) return;
  296. let data = {aEle, url, response};
  297. if (/^(2|3)/.test(response.status)) {
  298. $$cache[url] = response;
  299. aEle && $(aEle).attr('decoded', '');
  300. deferred.resolve(data);
  301. } else {
  302. deferred.reject(data);
  303. }
  304. aEle && $(aEle).removeAttr('decoding');
  305. },
  306. ontimeout: (response)=> {
  307. let data = {aEle, url, response};
  308. config.debug && console.error(data);
  309. aEle && $(aEle).removeAttr('decoding');
  310. deferred.reject(data);
  311. response && response.finalUrl ? deferred.resolve(data) : deferred.reject(data);
  312. },
  313. onerror: (response)=> {
  314. let data = {aEle, url, response};
  315. config.debug && console.error(data);
  316. aEle && $(aEle).removeAttr('decoding');
  317. response && response.finalUrl ? deferred.resolve(data) : deferred.reject(data);
  318. }
  319. });
  320.  
  321. return deferred.promise;
  322. };
  323.  
  324. /**
  325. * simple deferred object like angularJS $q or q promise library
  326. * @param fn promise function
  327. * @returns {Promise}
  328. */
  329. let $q = function (fn = noop) {
  330. return new Promise(fn);
  331. };
  332.  
  333. /**
  334. * generator a deferred object use like angularJS's $q service
  335. * @returns {{}}
  336. */
  337. $q.defer = function () {
  338. let deferred = {};
  339.  
  340. deferred.promise = new Promise(function (resolve, reject) {
  341. deferred.resolve = function (data) {
  342. resolve(data);
  343. };
  344. deferred.reject = function (data) {
  345. reject(data);
  346. };
  347. });
  348.  
  349. return deferred;
  350. };
  351.  
  352. // config
  353. const config = {
  354. rules: `
  355. a[href*="www.baidu.com/link?url"]
  356. :not(.m)
  357. :not([decoding])
  358. :not([decoded])
  359. `.trim().replace(/\n/img, '').replace(/\s{1,}([^a-zA-Z])/g, '$1'),
  360. debug: false
  361. };
  362.  
  363. let isDecodingAll = false;
  364.  
  365. /**
  366. * the main class to bootstrap this script
  367. */
  368. class main {
  369. constructor(agm = '') {
  370. if (!agm) return this;
  371.  
  372. this.inViewPort = [];
  373.  
  374. $(agm).each((ele) => {
  375. if (jqLite.fn.visible(ele)) this.inViewPort.push(ele);
  376. });
  377. }
  378.  
  379. /**
  380. * request a url which has origin links
  381. * @returns {Promise}
  382. */
  383. all() {
  384. var deferred = $q.defer();
  385.  
  386. let url = window.top.location.href.replace(/(\&)(tn=\w+)(\&)/img, '$1' + 'tn=baidulocal' + '$3');
  387.  
  388. isDecodingAll = true;
  389. $ajax(url)
  390. .then(function (data) {
  391. isDecodingAll = false;
  392.  
  393. if (!data.response) return;
  394. let response = data.response.responseText;
  395.  
  396. // remove the image which load with http not https
  397. response = response.replace(/src=[^>]*/, '');
  398.  
  399. let html = document.createElement('html');
  400. html.innerHTML = response;
  401.  
  402. $('.t>a:not(.OP_LOG_LINK):not([decoded])').each((sourceEle)=> {
  403. $('.f>a', html).each((targetEle) => {
  404. if ($(sourceEle).text === $(targetEle).text) {
  405. sourceEle.href = targetEle.href;
  406. $(sourceEle).attr('decoded', '');
  407. if (config.debug) sourceEle.style.background = 'green';
  408. }
  409. });
  410. });
  411.  
  412. deferred.resolve(data);
  413. }, function (data) {
  414. isDecodingAll = false;
  415. deferred.reject(data);
  416. });
  417.  
  418. return deferred.promise;
  419. }
  420.  
  421. /**
  422. * request the A tag's href one by one those in view port
  423. * @returns {main}
  424. */
  425. oneByOne() {
  426. $(this.inViewPort).each(function (aEle) {
  427. if (/www\.baidu\.com\/link\?url=/im.test(aEle.href) === false)return;
  428. $ajax(aEle.href, aEle)
  429. .then(function (data) {
  430. if (!data) return;
  431. data.aEle.href = data.response.finalUrl;
  432. if (config.debug) data.aEle.style.background = 'green';
  433. });
  434. });
  435. return this;
  436. }
  437.  
  438. }
  439.  
  440. console.info('去跳转启动...');
  441.  
  442. /**
  443. * bootstrap the script
  444. */
  445. $(()=> {
  446.  
  447. let init = ()=> {
  448. new main(config.rules).all()
  449. .then(function () {
  450. new main(config.rules).oneByOne();
  451. }, function () {
  452. new main(config.rules).oneByOne();
  453. });
  454. };
  455.  
  456. // init
  457. init();
  458.  
  459. let observeDebounce = jqLite.fn.debounce((target, addList, removeList) => {
  460. if (!addList || !addList.length) return;
  461. if (isDecodingAll === true) {
  462. new main(config.rules).oneByOne();
  463. } else {
  464. init();
  465. }
  466. }, 200);
  467. $(document).observe(function (target, addList, removeList) {
  468. observeDebounce(target, addList, removeList);
  469. });
  470.  
  471. let scrollDebounce = jqLite.fn.debounce(() => {
  472. new main(config.rules).oneByOne();
  473. }, 200);
  474. $(window).bind('scroll', ()=> {
  475. scrollDebounce();
  476. });
  477.  
  478. let overDebouce = jqLite.fn.debounce((e)=> {
  479. let aEle = e.target;
  480. if (aEle.tagName !== "A" || !aEle.href || !/w{3}\.baidu\.com\/link\?url=/im.test(aEle.href)) return;
  481. $ajax(aEle.href, aEle)
  482. .then(function (data) {
  483. data.aEle.href = data.response.finalUrl;
  484. });
  485. }, 100);
  486. $(document).bind('mouseover', (e) => {
  487. overDebouce(e);
  488. });
  489.  
  490.  
  491. });
  492.  
  493. })(window, document);
  494.  
  495.  
  496. /* jshint ignore:end */
  497.  
  498.  
  499.