remove the jump link in BAIDU (ECMA6)

去除百度搜索跳转链接

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name              remove the jump link in BAIDU (ECMA6)
// @author            axetroy
// @description       去除百度搜索跳转链接
// @version           2016.4.10.1
// @grant             GM_xmlhttpRequest
// @include           *www.baidu.com*
// @connect           tags
// @connect           *
// @compatible        chrome  完美运行
// @compatible        firefox  完美运行
// @supportURL        http://www.burningall.com
// @run-at            document-start
// @contributionURL   [email protected]|alipay.com
// @namespace         https://greasyfork.org/zh-CN/users/3400-axetroy
// @license           The MIT License (MIT); http://opensource.org/licenses/MIT
// ==/UserScript==


if (typeof require !== 'undefined' && typeof require === 'function') {
  require("babel-polyfill");
}

/* jshint ignore:start */
;(function (window, document) {

  'use strict';

  var ES6Support = true;

  try {
    let test_let = true;
    const test_const = true;
    var test_tpl_str = `233`;
    var test_arrow_fn = (a = '233') => {
    };
    var test_promise = new Promise(function (resolve, reject) {
      resolve();
    });
    class test_class {

    }
  } catch (e) {
    /**
     * 促进大家升级浏览器,拯救前端,就是拯救我自己
     */
    alert('你的浏览器不支持ECMA6,去除百度搜索跳转链接将失效,请升级浏览器和脚本管理器');
    ES6Support = false;
  }

  if (!ES6Support) return;

  let noop = () => {
  };

  /**
   * a lite jquery mock
   */
  class jqLite {
    constructor(selectors = '', context = document) {
      this.selectors = selectors;
      this.context = context;
      this.length = 0;

      switch (typeof selectors) {
        case 'undefined':
          break;
        case 'string':
          Array.from(context.querySelectorAll(selectors), (ele, i) => {
            this[i] = ele;
          this.length++;
      }, this);
      break;
    case 'object':
      if (selectors.length) {
        Array.from(selectors, (ele, i) => {
          this[i] = ele;
        this.length++;
      }, this);
      } else {
        this[0] = selectors;
        this.length = 1;
      }
      break;
    case 'function':
      this.ready(selectors);
      break;
    default:

    }

    };

    each(fn = noop) {
      for (let i = 0; i < this.length; i++) {
        fn.call(this, this[i], i);
      }
      return this;
    };

    bind(types = '', fn = noop) {
      this.each((ele)=> {
        types.trim().split(/\s{1,}/).forEach((type)=> {
        ele.addEventListener(type, (e) => {
        let target = e.target || e.srcElement;
      if (fn.call(target, e) === false) {
        e.returnValue = true;
        e.cancelBubble = true;
        e.preventDefault && e.preventDefault();
        e.stopPropagation && e.stopPropagation();
        return false;
      }
    }, false);
    });
    });
    };

    ready(fn = noop) {
      this.context.addEventListener('DOMContentLoaded', e => {
        fn.call(this);
    }, false);
    }

    observe(fn = noop, config = {childList: true, subtree: true}) {
      this.each((ele) => {
        let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
      let observer = new MutationObserver((mutations) => {
          mutations.forEach((mutation) => {
          fn.call(this, mutation.target, mutation.addedNodes, mutation.removedNodes);
    });
    });
      observer.observe(ele, config);
    });
      return this;
    };

    attr(attr, value) {
      // one agm
      if (!arguments.length === 1) {
        // get attr value
        if (typeof attr === 'string') {
          return this[0].getAttribute(attr);
        }
        // set attr with a json
        else if (typeof attr === 'object') {
          this.each(function (ele) {
            for (let at in attr) {
              if (attr.hasOwnProperty(at)) {
                ele.setAttribute(at, value);
              }
            }
          });
        }
        return value;
      }
      // set
      else if (arguments.length === 2) {
        this.each(function (ele) {
          ele.setAttribute(attr, value);
        });
        return this;
      }
      else {
        return this;
      }
    };

    removeAttr(attr) {
      if (arguments.length === 1) {
        this.each((ele)=> {
          ele.removeAttribute(attr);
      });
      }
      return this;
    }

    get text() {
      let ele = this[0];
      return ele.innerText ? ele.innerText : ele.textContent;
    };

    static get fn() {
      let visible = (ele)=> {
        let pos = ele.getBoundingClientRect();
        let w;
        let h;
        let inViewPort;
        let docEle = document.documentElement;
        let docBody = document.body;
        if (docEle.getBoundingClientRect) {
          w = docEle.clientWidth || docBody.clientWidth;
          h = docEle.clientHeight || docBody.clientHeight;
          inViewPort = pos.top > h || pos.bottom < 0 || pos.left > w || pos.right < 0;
          return inViewPort ? false : true;
        }
      };
      let debounce = (fn, delay)=> {
        let timer;
        return function () {
          let agm = arguments;
          window.clearTimeout(timer);
          timer = window.setTimeout(()=> {
              fn.apply(this, agm);
        }, delay);
        }
      };
      return {
        visible,
        debounce
      }
    };

  }

  let $ = (selectors = '', context = document) => {
    return new jqLite(selectors, context);
  };

  /**
   * cache the ajax response result
   * @type {{}}
   */
  let $$cache = {};

  /**
   * timeout wrapper
   * @param fn
   * @param delay
   * @returns {number}
   */
  let $timeout = (fn = noop, delay = 0) => {
    return window.setTimeout(fn, delay);
  };

  /**
   * cancel timer
   * @param timerId
   * @returns {*}
   */
  $timeout.cancel = function (timerId) {
    window.clearTimeout(timerId);
    return timerId;
  };

  let $$count = 0;

  /**
   * ajax function
   * @param url           the url request
   * @param aEle          the A link element [non essential variables]
   * @returns {Promise}
   */
  let $ajax = (url, aEle) => {
    var deferred = $q.defer();

    // if in BAIDU home page
    if (new RegExp(`${window.location.host}\/?$`, 'im').test(url)) {
      $timeout(function () {
        deferred.resolve({aEle, url, response: ''});
        return deferred.promise;
      });
    }

    // if has cache
    if ($$cache[url]) {
      $timeout(function () {
        deferred.resolve({aEle, url, response: $$cache[url]});
        return deferred.promise;
      });
    }

    // not match the url
    if (!/w{3}\.baidu\.com\/link\?url=/im.test(url) && !/w{3}\.baidu\.com\/s/.test(url)) {
      $timeout(function () {
        deferred.resolve({aEle, url, response: {finalUrl: url}});
        return deferred.promise;
      });
    }

    // make the protocol agree
    if (!new RegExp(`^${window.location.protocol}`).test(url)) {
      url = url.replace(/^(http|https):/im, window.location.protocol);
    }

    if (config.debug) console.info(`${$$count++}-ajax:${url}`);

    if (aEle) $(aEle).attr('decoding', '');

    GM_xmlhttpRequest({
        method: "GET",
        url: url,
        // timeout: 5000,
        anonymous: !!aEle,
        onreadystatechange: function (response) {
          if (response.readyState !== 4) return;
          let data = {aEle, url, response};
          if (/^(2|3)/.test(response.status)) {
            $$cache[url] = response;
            aEle && $(aEle).attr('decoded', '');
            deferred.resolve(data);
          } else {
            deferred.reject(data);
          }
          aEle && $(aEle).removeAttr('decoding');
        },
        ontimeout: (response)=> {
        let data = {aEle, url, response};
    config.debug && console.error(data);
    aEle && $(aEle).removeAttr('decoding');
    deferred.reject(data);
    response && response.finalUrl ? deferred.resolve(data) : deferred.reject(data);
  },
    onerror: (response)=> {
      let data = {aEle, url, response};
      config.debug && console.error(data);
      aEle && $(aEle).removeAttr('decoding');
      response && response.finalUrl ? deferred.resolve(data) : deferred.reject(data);
    }
  });

    return deferred.promise;
  };

  /**
   * simple deferred object like angularJS $q or q promise library
   * @param fn                 promise function
   * @returns {Promise}
   */
  let $q = function (fn = noop) {
    return new Promise(fn);
  };

  /**
   * generator a deferred object use like angularJS's $q service
   * @returns {{}}
   */
  $q.defer = function () {
    let deferred = {};

    deferred.promise = new Promise(function (resolve, reject) {
      deferred.resolve = function (data) {
        resolve(data);
      };
      deferred.reject = function (data) {
        reject(data);
      };
    });

    return deferred;
  };

  // config
  const config = {
    rules: `
      a[href*="www.baidu.com/link?url"]
      :not(.m)
      :not([decoding])
      :not([decoded])
    `.trim().replace(/\n/img, '').replace(/\s{1,}([^a-zA-Z])/g, '$1'),
    debug: false
  };

  let isDecodingAll = false;

  /**
   * the main class to bootstrap this script
   */
  class main {
    constructor(agm = '') {
      if (!agm) return this;

      this.inViewPort = [];

      $(agm).each((ele) => {
        if (jqLite.fn.visible(ele)) this.inViewPort.push(ele);
    });
    }

    /**
     * request a url which has origin links
     * @returns {Promise}
     */
    all() {
      var deferred = $q.defer();

      let url = window.top.location.href.replace(/(\&)(tn=\w+)(\&)/img, '$1' + 'tn=baidulocal' + '$3');

      isDecodingAll = true;
      $ajax(url)
        .then(function (data) {
          isDecodingAll = false;

          if (!data.response) return;
          let response = data.response.responseText;

          // remove the image which load with http not https
          response = response.replace(/src=[^>]*/, '');

          let html = document.createElement('html');
          html.innerHTML = response;

          $('.t>a:not(.OP_LOG_LINK):not([decoded])').each((sourceEle)=> {
            $('.f>a', html).each((targetEle) => {
            if ($(sourceEle).text === $(targetEle).text) {
            sourceEle.href = targetEle.href;
            $(sourceEle).attr('decoded', '');
            if (config.debug) sourceEle.style.background = 'green';
          }
        });
        });

          deferred.resolve(data);
        }, function (data) {
          isDecodingAll = false;
          deferred.reject(data);
        });

      return deferred.promise;
    }

    /**
     * request the A tag's href one by one those in view port
     * @returns {main}
     */
    oneByOne() {
      $(this.inViewPort).each(function (aEle) {
        if (/www\.baidu\.com\/link\?url=/im.test(aEle.href) === false)return;
        $ajax(aEle.href, aEle)
          .then(function (data) {
            if (!data) return;
            data.aEle.href = data.response.finalUrl;
            if (config.debug) data.aEle.style.background = 'green';
          });
      });
      return this;
    }

  }

  console.info('去跳转启动...');

  /**
   * bootstrap the script
   */
  $(()=> {

    let init = ()=> {
    new main(config.rules).all()
      .then(function () {
        new main(config.rules).oneByOne();
      }, function () {
        new main(config.rules).oneByOne();
      });
};

  // init
  init();

  let observeDebounce = jqLite.fn.debounce((target, addList, removeList) => {
      if (!addList || !addList.length) return;
  if (isDecodingAll === true) {
    new main(config.rules).oneByOne();
  } else {
    init();
  }
}, 200);
  $(document).observe(function (target, addList, removeList) {
    observeDebounce(target, addList, removeList);
  });

  let scrollDebounce = jqLite.fn.debounce(() => {
      new main(config.rules).oneByOne();
}, 200);
  $(window).bind('scroll', ()=> {
    scrollDebounce();
});

  let overDebouce = jqLite.fn.debounce((e)=> {
      let aEle = e.target;
  if (aEle.tagName !== "A" || !aEle.href || !/w{3}\.baidu\.com\/link\?url=/im.test(aEle.href)) return;
  $ajax(aEle.href, aEle)
    .then(function (data) {
      data.aEle.href = data.response.finalUrl;
    });
}, 100);
  $(document).bind('mouseover', (e) => {
    overDebouce(e);
});


});

})(window, document);


/* jshint ignore:end */