TrackingParameterRemover

remove tracking parameters

目前为 2024-09-14 提交的版本。查看 最新版本

// ==UserScript==
// @name        TrackingParameterRemover
// @namespace   https://htsign.hateblo.jp
// @version     20240915-rev0
// @description remove tracking parameters
// @author      htsign
// @match       *://*/*
// @run-at      document-start
// @grant       GM_registerMenuCommand
// ==/UserScript==

const createStyle = params => {
  const toKebab = s => s.replace(/(?<=[a-z])[A-Z]/g, m => `-${m.toLowerCase()}`);
  return Object.entries(params)
    .reduce((acc, [key, val]) => ({ ...acc, [toKebab(key)]: val }), {});
};

GM_registerMenuCommand('open settings', ev => {
  const PADDING = 20;

  const wrapper = Object.assign(
    document.createElement('div'),
    {
      style: createStyle({
        position: 'absolute',
        left: PADDING,
        top: PADDING,
      }),
    },
  );
  const shadowRoot = wrapper.shadowRoot ?? wrapper.attachShadow({ mode: 'open' });

  document.body.append(wrapper);
});

(function() {
  'use strict';

  const TRACKING_TAGS = [
    '#?utm_campaign',
    '#?utm_content',
    '#?utm_int',
    '#?utm_medium',
    '#?utm_source',
    '_hsmi',
    '_openstat',
    'action_object_map',
    'action_ref_map',
    'action_type_map',
    'fb_action_ids',
    'fb_action_types',
    'fb_ref',
    'fb_source',
    'ga_campaign',
    'ga_content',
    'ga_medium',
    'ga_place',
    'ga_source',
    'ga_term',
    'gs_l',
    'guccounter',
    'guce_referrer',
    'guce_referrer_sig',
    'gws_rd',
    'hmb_campaign',
    'hmb_medium',
    'hmb_source',
    'ref_src',
    'ref_url',
    'utm_campaign',
    'utm_cid',
    'utm_content',
    'utm_int',
    'utm_language',
    'utm_medium',
    'utm_name',
    'utm_place',
    'utm_pubreferrer',
    'utm_reader',
    'utm_source',
    'utm_swu',
    'utm_term',
    'utm_userid',
    'utm_viz_id',
    'yclid',
    '[email protected]',
    '_encoding@amazon.*',
    'linkCode@amazon.*',
    'linkId@amazon.*',
    'pd_rd_*@amazon.*',
    'psc@amazon.*',
    'qid@amazon.*',
    'sbo@amazon.*',
    'sprefix@amazon.*',
    'sr@amazon.*',
    'tag@amazon.*',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    'bi?@google.*',
    'client@google.*',
    'dpr@google.*',
    'ei@google.*',
    'gws_rd@google.*',
    'oq@google.*',
    'sa@google.*',
    'sei@google.*',
    'source@google.*',
    'ved@google.*',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    'ref@*.nicovideo.jp',
    'ss_id@*.nicovideo.jp',
    'ss_pos@*.nicovideo.jp',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]',
    '[email protected]/watch',
  ];

  /**
   * @param {Location} loc
   * @returns {{ url: string, locationChanged: boolean }}
   */
  const removeTracking = loc => {
    let locationChanged = false;
    const escapables = Object.freeze({
      '.': '\.',
    });
    const wildcardCharacters = Object.freeze({
      '*': '.*',
      '?': '.',
    });
    const wildcardKeys = Object.keys(wildcardCharacters);

    /**
     * @param {string} pattern
     * @returns {RegExp}
     */
    const toRegExp = pattern => {
      /**
       *
       * @param {Record<string, string>} table
       * @param {string} s
       * @returns {string}
       */
      const replace = (table, s) => Object.entries(table).reduce((acc, [f, t]) => acc.split(f).join(t), s);

      const sanitized = replace(escapables, pattern);
      const inner = replace(wildcardCharacters, sanitized);
      return new RegExp('^' + inner + '$', 'i');
    };

    /**
     *
     * @param {string} pattern
     * @param {string} s
     * @returns {boolean}
     */
    const match = (pattern, s) => {
      if (wildcardKeys.some(c => pattern.includes(c))) {
        return toRegExp(pattern).test(s);
      }
      return pattern === s;
    };

    /**
     * @param {string} domain
     * @param {URLSearchParams} params
     * @param {string} pattern
     * @returns {string}
     */
    const deleteKeys = (domain, params, pattern) => {
      if (!domain || loc.hostname.split('.').some((_, i, arr) => match(domain, arr.slice(i).join('.')))) {
        for (const [key] of params) {
          if (match(pattern, key)) {
            params.delete(key);
            locationChanged = true;
          }
        }
      }
      return params.toString().split('%25').join('%');
    };

    /**
     * @param {string} search
     * @param {() => boolean} condition
     * @param {(arg: URLSearchParams) => any} callback
     * @returns {void}
     */
    const proc = (search, condition, callback) => {
      const params = new URLSearchParams(search);
      if (params.size === 0) return;
      if (!condition()) return;
      callback(params);
    };

    const url = new URL(loc);
    url.search = url.search.split('%25').join('\0'); // avoid to escape of original '%25'
    TRACKING_TAGS.forEach(tag => {
      const [t, domain, pathname] = tag.split(/[@\/]/);

      if (t.startsWith('#?')) {
        proc(
          url.hash.slice(1),
          () => pathname == null || url.pathname === `/${pathname}`,
          params => {
            url.hash = deleteKeys(domain, params, t.slice(2));
          },
        );
      }
      else {
        if (!url.search) return;

        proc(
          url.search.slice(1).replace(/%(?!25)/g, '%25'),
          () => pathname == null || url.pathname === `/${pathname}`,
          params => {
            url.search = deleteKeys(domain, params, t);
          },
        );
      }
    });
    url.search = url.search.split('%00').join('%25'); // restore original '%25'
    return { url: url.href, locationChanged };
  };

  const { url, locationChanged } = removeTracking(location);
  if (locationChanged) {
    console.info(`TrackingParameterRemover: {${location.href}} => {${url}}`);
    location.replace(url);
  }
}());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址