Clean URL Improved

Remove tracking parameters and redirect to original URL. This Userscript uses the URL Interface instead of RegEx.

目前為 2023-07-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Clean URL Improved
// @namespace   i2p.schimon.cleanurl
// @description Remove tracking parameters and redirect to original URL. This Userscript uses the URL Interface instead of RegEx.
// @homepageURL https://openuserjs.org/scripts/sjehuda/Clean_URL_Improved
// @supportURL  https://openuserjs.org/scripts/sjehuda/Clean_URL_Improved/issues
// @copyright   2023, Schimon Jehudah (http://schimon.i2p)
// @license     MIT; https://opensource.org/licenses/MIT
// @grant       none
// @run-at      document-end
// @match       file://*
// @match       *://*/*
// @version     23.07.08
// @icon        data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5qlPC90ZXh0Pjwvc3ZnPgo=
// ==/UserScript==

// @grant        GM.setValue
// @grant        GM.getValue

/*

NEWS

Handle /ref=

Added .replace(/\/ref=/g, '/?ref=')

Coding like a Japanese

TODO

Delete empty parameters

Statistics
GM.getValue('links-total')
GM.getValue('links-bad')
GM.getValue('links-good')
GM.getValue('parameters-total')
GM.getValue('parameters-bad')
GM.getValue('parameters-good')
GM.getValue('parameters-urls')
GM.getValue('parameters-unclassified')

GM.setValue('pbl', 'hostname-parameter')
GM.setValue('pwl', 'hostname-parameter')

FIXME

Whitelisted parameters without values are realized to purged-url

*/

/*

Simple version of this Userscript
let url = new URL(location.href);
if (url.hash || url.search) {
  location.href = url.origin + url.pathname
};

*/

// https://openuserjs.org/scripts/tfr/YouTube_Link_Cleaner

// Check whether HTML; otherwise, exit.
//if (!document.contentType == 'text/html')
if (document.doctype == null) return;

//let point = [];
const namespace = 'i2p.schimon.cleanurl';

// List of url parameters
const urls = [
  'ap_id',
  'redirect',
  'ref',
  'source',
  'src',
  'url',
  'utm_source',
  'utm_term'];

// List of alphabet
const alphabet = 'abcdefghijklmnopqrstuvwxyz';

// List of reserved parameters
const whitelist = [
  'act',                  // invision board
  'activeTab',            // npmjs
  'art',                  // article
  'artist',               // bandcamp jamendo
  'action',               // bugzilla
  'author',               // git
  'ap_id',                // activitypub
  'bill',                 // law
  'board',                // simple machines
  'category',             // id
  'categories',           // searxng
  'catid',                // id
  'code',                 // code
  'component',            // addons.palemoon.org
  'content',              // id
  'CurrentMDW',           // menuetos.be
  'dark',                 // yorik.uncreated.net
  'date',                 // date
  'days',                 // wiki
  'diff',                 // wiki
  'do_union',             // bugzilla
  'district',             // house.mo.gov
  'exp_time',             // cdn
  'expires',              // cdn
  'ezimgfmt',             // cdn image processor
  'feedformat',           // wiki
  'fid',                  // mybb
  'file_host',            // cdn
  'filename',             // filename
  'for',                  // cdn
  'format',               // file type
  'from',                 // redmine
  'guid',                 // guid
  'group',                // bugzilla
  'group_id',             // sourceforge
  'hash',                 // cdn
  'hidebots',             // wiki
  'hl',                   // language
  'id',                   // id
  'ie',                   // character encoding
  'ip',                   // ip address
  'item_class',           // greasyfork
  'item_id',              // greasyfork
  'iv_load_policy',       // invidious
  'jid',                  // jabber id (xmpp)
  'key',                  // cdn
  'lang',                 // language
  'language',             // searxng
  'library',              // oujs
  'limit',                // wiki
  'locale',               // locale
  'logout',               // bugzilla
  'lr',                   // cdn
  'lra',                  // cdn
  'member',               // xmb forum
  'name',                 // archlinux
  'mobileaction',         // wiki
  'news_id',              // post
  'oldid',                // wiki
  'order',                // bugzilla
  'orderBy',              // oujs
  'orderDir',             // oujs
//'p',                    // search query / page number
  'page',                 // mybb
  'pid',                  // fluxbb
  'preferencesReturnUrl', // return url
  'product',              // bugzilla
  'profile',              // copy.sh/v86/
//'q',                    // search query
  'query',                // search query
  'query_format',         // bugzilla
  'redlink',              // wiki
//'referer',              // signin NOTE provided pathname contains login (log-in) or signin (sign-in)
  'resolution',           // bugzilla
  'requestee',            // bugzilla
  'requester',            // bugzilla
  'return_to',            // signin
//'s',                    // search query
  'search',               // search query
  'showtopic',            // invision board
  'show_all_versions',    // greasyfork
  'sign',                 // cdn
  'signature',            // cdn
  'sort',                 // greasyfork
  'speed',                // cdn
  'st',                   // invision board
  'start_time',           // media playback
  'state',                // cdn
  '__switch_theme',       // theme (theanarchistlibrary.org)
  'tag',                  // id
  'template',             // zapier
  'tid',                  // mybb
  'title',                // send (share) links and wiki
  'topic',                // simple machines
  'type',                 // file type
//'url',                  // url NOTE not sure whether to whitelist or blacklist
  'utf8',                 // encoding
  'urlversion',           // wiki
  'version',              // greasyfork
//'view',                 // invision board
//'_x_tr_sl', // translate online service
//'_x_tr_tl=', // translate online service
//'_x_tr_hl=', // translate online service
//'_x_tr_pto', // translate online service
//'_x_tr_hist', // translate online service
  'year'                  // year
  ];

// List of useless hash
const hash = [
  'back-url',
  'intcid',
  'niche-',
//'searchinput',
  'src'];

// List of useless parameters
const blacklist = [
  'ad',
  'ad_medium',
  'ad_name',
  'ad_pvid',
  'ad_sub',
//'ad_tags',
  'advertising-id',
//'aem_p4p_detail',
  'af',
  'aff',
  'aff_fcid',
  'aff_fsk',
  'aff_platform',
  'aff_trace_key',
  'affparams',
  'afSmartRedirect',
  'afftrack',
  'affparams',
//'aid',
  'algo_exp_id',
  'algo_pvid',
  'ar',
//'ascsubtag',
//'asc_contentid',
  'asgtbndr',
  'atc',
  'ats',
  'autostart',
//'b64e', // breaks yandex
  'bizType',
//'block',
  'bta',
  'businessType',
  'campaign',
  'campaignId',
//'__cf_chl_rt_tk',
//'cid',  // breaks sacred magick
  'ck',
//'clickid',
//'client_id',
//'cm_ven',
  'content-id',
  'crid',
  'cst',
  'cts',
  'curPageLogUid',
//'data', // breaks yandex
//'dchild',
//'dclid',
  'deals-widget',
  'dgcid',
  'dicbo',
//'dt',
  'edd',
  'edm_click_module',
//'ei',
//'embed',
  '_encoding',
//'etext', // breaks yandex
  'eventSource',
  'fbclid',
  'feature',
  'forced_click',
//'fr',
  'frs',
//'from', // breaks yandex
  '_ga',
  'ga_order',
  'ga_search_query',
  'ga_search_type',
  'ga_view_type',
  'gatewayAdapt',
//'gclid',
//'gclsrc',
  'gh_jid',
  'gps-id',
//'gs_lcp',
  'gt',
  'guccounter',
  'hdtime',
  'ICID',
  'ico',
  'ig_rid',
//'idzone',
//'iflsig',
  'intcmp',
  'irclickid',
//'irgwc',
//'irpid',
  'itid',
//'itok',
//'katds_labels',
//'keywords',
  'keyno',
  'l10n',
  'linkCode',
  'mc',
  'mid',
  '__mk_de_DE',
  'mp',
  'nats',
  'nci',
  'obOrigUrl',
  'offer_id',
  'optout',
  'oq',
  'organic_search_click',
  'pa',
  'Partner',
  'partner',
  'partner_id',
  'pcampaignid',
  'pd_rd_i',
  'pd_rd_r',
  'pd_rd_w',
  'pd_rd_wg',
  'pdp_npi',
  'pf_rd_i',
  'pf_rd_m',
  'pf_rd_p',
  'pf_rd_r',
  'pf_rd_s',
  'pf_rd_t',
  'pg',
  'PHPSESSID',
  'pk_campaign',
  'pdp_ext_f',
  'pkey',
  'platform',
  'plkey',
  'pqr',
  'pr',
  'pro',
  'prod',
  'prom',
  'promo',
  'promocode',
  'promoid',
  'psc',
  'psprogram',
  'pvid',
  'qid',
//'r',
  'realDomain',
  'recruiter_id',
  'redirect',
  'ref',
  'ref_',
  'ref_src',
  'refcode',
  'referrer',
  'refinements',
  'reftag',
  'rnid',
  'rowan_id1',
  'rowan_msg_id',
//'rss',
//'sCh',
  'sclient',
  'scm',
  'scm_id',
  'scm-url',
//'sd',
  'sh',
  'shareId',
  'showVariations',
  'si',
//'sid', // breaks whatsup.org.il
  '___SID',
//'site_id',
  'sk',
  'smid',
  'social_params',
  'source',
  'sourceId',
  'sp_csd',
  'spLa',
  'spm',
  'spreadType',
//'sprefix',
  'sr',
  'src',
  '_src',
  'src_cmp',
  'src_player',
  'src_src',
  'srcSns',
  'su',
//'sxin_0_pb',
  '_t',
//'tag',
  'tcampaign',
  'td',
  'terminal_id',
//'text',
  'th', // Sometimes restored after page load
//'title',
  'tracelog',
  'traffic_id',
  'traffic_type',
  'tt',
  'uact',
  'ug_edm_item_id',
  'utm',
//'utm1',
//'utm2',
//'utm3',
//'utm4',
//'utm5',
//'utm6',
//'utm7',
//'utm8',
//'utm9',
  'utm_campaign',
  'utm_content',
  'utm_medium',
  'utm_source',
  'utm_term',
  'uuid',
//'utype',
//'ve',
//'ved',
//'zone'
  ];

// URL Indexers
const paraIDX = [
  'algo_exp_id',
  'algo_pvid',
  'b64e',
  'cst',
  'cts',
  'data',
  'ei',
//'etext',
  'from',
  'iflsig',
  'gbv',
  'gs_lcp',
  'hdtime',
  'keyno',
  'l10n',
  'mc',
  'oq',
//'q',
  'sei',
  'sclient',
  'sign',
  'source',
  'state',
//'text',
  'uact',
  'uuid',
  'ved'];

// Market Places 
const paraMKT = [
  '___SID',
  '_t',
  'ad_pvid',
  'af',
  'aff_fsk',
  'aff_platform',
  'aff_trace_key',
  'afSmartRedirect',
  'bizType',
  'businessType',
  'ck',
  'content-id',
  'crid',
  'curPageLogUid',
  'deals-widget',
  'edm_click_module',
  'gatewayAdapt',
  'gps-id',
  'keywords',
  '__mk_de_DE',
  'pd_rd_i',
  'pd_rd_r',
  'pd_rd_w',
  'pd_rd_wg',
  'pdp_npi',
  'pf_rd_i',
  'pf_rd_m',
  'pf_rd_p',
  'pf_rd_r',
  'pf_rd_s',
  'pf_rd_t',
  'platform',
  'pdp_ext_f',
  'ref_',
  'refinements',
  'rnid',
  'rowan_id1',
  'rowan_msg_id',
  'scm',
  'scm_id',
  'scm-url',
  'shareId',
//'showVariations',
  'sk',
  'smid',
  'social_params',
  'spLa',
  'spm',
  'spreadType',
  'sr',
  'srcSns',
//'sxin_0_pb',
  'terminal_id',
  'th', // Sometimes restored after page load
  'tracelog',
  'tt',
  'ug_edm_item_id'];

// IL
const paraIL = [
  'dicbo',
  'obOrigUrl'];

// General
const paraWWW = [
  'aff',
  'promo',
  'promoid',
  'ref',
  'utm_campaign',
  'utm_content',
  'utm_medium',
  'utm_source',
  'utm_term'];

// For URL of the Address bar
// Check and modify page address
// TODO Add bar and ask to clean address bar
(function modifyURL() {

  let
    check = [],
    // NOTE Marketplace website which uses /ref= instead of ?ref=
    // location.href.replace(/\/ref=/g, '/?ref=');
    url = new URL(location.href.replace('/ref=', '/?ref='));

  // TODO turn into boolean function
  for (let i = 0; i < blacklist.length; i++) {
    if (url.searchParams.get(blacklist[i])) {
      check.push(blacklist[i]);
      url.searchParams.delete(blacklist[i]);
      //newURL = url.origin + url.pathname + url.search + url.hash;
    }
  }

  // TODO turn into boolean function
  for (let i = 0; i < hash.length; i++) {
    if (url.hash.startsWith('#' + hash[i])) {
      check.push(hash[i]);
      //newURL = url.origin + url.pathname + url.search;
    }
  }

  if (check.length > 0) {
    let newURL = url.origin + url.pathname + url.search;
    window.history.pushState(null, null, newURL);
    //location.href = newURL;
  }

})();

// NOTE Marketplace website which uses /ref= instead of ?ref=
(function correctSlashRefURLs() {
  for (let i = 0; i < document.links.length; i++) {
    if (document.links[i].href.includes('/ref=')) {
      document.links[i].href = document.links[i].href.replace('/ref=', '/?ref=');
      document.links[i].setAttribute('was-ref', '');
    }
  }
})();

(function scanAllURLs() {
  for (let i = 0; i < document.links.length; i++) {
    let url = new URL(document.links[i].href);
    // NOTE Consider BitTorrent Magnet links
    // removing trackers would need a warning about
    // private torrents, if torrent is not public (dht-enabled)
    const allowedProtocols = [
      'finger:', 'freenet:', 'gemini:',
      'gopher:', 'wap:', 'ipfs:',
      'https:', 'ftps:', 'http:', 'ftp:'];
    if (url.search && allowedProtocols.includes(url.protocol)) {
    //if (url.search || url.hash) {
      document.links[i].setAttribute('href-data', document.links[i].href);
    }
  }
})();

(function scanBadURLs() {
  for (let i = 0; i < document.links.length; i++) {
    // TODO callback, Mutation Observer, and Event Listener
    // TODO Count links increaseByOne('links')
    // NOTE To count links, add return statement to function cleanLink()
    // return statement will indicate that link is positive for subject
    // parameters and therefore should be counted. Counter will be added
    // by one, once detected that url is not equal to (new) url.
    hash.forEach(j => cleanLink(document.links[i], j, 'hash'));
    blacklist.forEach(j => cleanLink(document.links[i], j, 'para'));
  }
})();

// TODO Add an Event Listener
function cleanLink(link, target, type) {
  let url = new URL(link.href);
  switch (type) {
    case 'hash':
      //console.log('hash ' + i)
      if (url.hash.startsWith('#' + target)) {
        //link.setAttribute('href-data', link.href);
        link.href = url.origin + url.pathname + url.search;
        //increaseByOne('hashes')
      }
      break;
    case 'para':
      //console.log('para ' + i)
      if (url.searchParams.get(target)) { 
        url.searchParams.delete(target);
        //link.setAttribute('href-data', link.href);
        link.href = url.origin + url.pathname + url.search;
        //increaseByOne('parameters')
      }
      break;
  }

  /*
  // EXTRA
  // For URL of hyperlinks
  for (const a of document.querySelectorAll('a')) {
    try{
      let url = new URL(a.href);
      for (let i = 0; i < blacklist.length; i++) {
        if (url.searchParams.get(blacklist[i])) {
          url.searchParams.delete(blacklist[i]);
        }
      }
      a.href = url;
    } catch (err) {
      //console.warn('Found no href for element: ' + a);
      //console.error(err);
    }
  } */

}

// TODO Hunt (for any) links within attributes using getAttributeNames()[i]

// Event Listener
// TODO Scan 'e.target.childNodes' until 'href-data' (link) is found
document.body.addEventListener("mouseover", function(e) { // mouseover works with keyboard too
  //if (e.target && e.target.nodeName == "A") {
  let hrefData = e.target.getAttribute('href-data');
  //if (e.target && hrefData && !document.getElementById(namespace)) {
  if (e.target && hrefData && hrefData != document.getElementById('url-original')) {
    if (document.getElementById(namespace)) {
      document.getElementById(namespace).remove();
    }
    selectionItem = createButton(e.pageX, e.pageY, hrefData);
    hrefData = new URL(hrefData);
    selectionItem.append(purgeURL(hrefData));
    let types = ['whitelist', 'blacklist', 'original']
    for (let i = 0; i < types.length; i++) {
      let button = purgeURL(hrefData, types[i]);
      if (types[i] == 'original' && e.target.getAttribute('was-ref') == '') {
        button.href = button.href.replace('/?ref=', '/ref=');
      }
      let exist;
      selectionItem.childNodes.forEach(
        node => {
          if (button.href == node.href) {
            exist = true;
          }
        }
      )
      if (!exist) {
        selectionItem.append(button);
      }
    }

    // Check for URLs
    for (let i = 0; i < urls.length; i++) {
      if (hrefData.searchParams.get(urls[i])) { // hrefData.includes('url=')
        urlParameter = hrefData.searchParams.get(urls[i]);
        try {
          urlParameter = new URL (urlParameter);
        } catch {
          if (urlParameter.includes('.')) {  // NOTE It is a guess
            try {
              urlParameter = new URL ('http:' + urlParameter);
            } catch {}
          }
        }
        if (typeof urlParameter == 'object' && // confirm url object
            urlParameter != location.href) {   // provided url isn't the same as of page
          newURLItem = extractURL(urlParameter);
          selectionItem.prepend(newURLItem);
        }
      }
    }

    /*
    // compare original against purged
    //if (selectionItem.querySelector(`#url-purged`) &&
    //    selectionItem.querySelector(`#url-original`)) {
    if (selectionItem.querySelector(`#url-purged`)) {
      //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
      let urlPurge = new URL (selectionItem.querySelector(`#url-purged`).href);
      // NOTE
      // These "searchParams.sort" ~~may be~~ *are not* redundant.
      // See resUrl.searchParams.sort()
      urlPurge.searchParams.sort();
      hrefData.searchParams.sort();
      //console.log(hrefData.search);
      //console.log(urlPurge.search);
      if (hrefData.search == urlPurge.search &&
          selectionItem.querySelector(`#url-original`)) {
        selectionItem.querySelector(`#url-original`).remove();
      }
    } else
    // compare original against safe
    if (selectionItem.querySelector(`#url-known`)) {
      //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
      let urlKnown = new URL (selectionItem.querySelector(`#url-known`).href);
      // NOTE
      // These "searchParams.sort" ~~may be~~ *are not* redundant.
      // See resUrl.searchParams.sort()
      urlKnown.searchParams.sort();
      hrefData.searchParams.sort();
      //console.log(hrefData.search);
      //console.log(urlKnown.search);
      if (hrefData.search == urlKnown.search &&
          selectionItem.querySelector(`#url-original`)) {
        selectionItem.querySelector(`#url-original`).remove();
      }
    }
    */

    // compare original against safe and purged
    // NOTE on "item.href = decodeURI(resUrl)"
    // The solution was here.
    // Decode was not the issue
    // This is a good example to show that
    // smaller tasks are as important as bigger tasks
    let urlsToCompare = ['#url-known', '#url-purged'];
    for (let i = 0; i < urlsToCompare.length; i++) {
      if (selectionItem.querySelector(urlsToCompare[i])) {
        //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
        let urlToCompare = new URL (selectionItem.querySelector(urlsToCompare[i]).href);
        // NOTE
        // These "searchParams.sort" ~~may be~~ *are not* redundant.
        // See resUrl.searchParams.sort()
        urlToCompare.searchParams.sort();
        hrefData.searchParams.sort();
        //console.log(hrefData.search);
        //console.log(urlToCompare.search);
        if (hrefData.search == urlToCompare.search &&
            selectionItem.querySelector(`#url-original`)) {
          selectionItem.querySelector(`#url-original`).remove();
        }
      }
    }

    // do not add element, if url has only whitelisted parameters and no potential url
    // add element, only if a potential url or non-whitelisted parameter was found
    let urlTypes = ['url-extracted', 'url-original', 'url-purged'];
    for (let i = 0; i < urlTypes.length; i++) {
      if (selectionItem.querySelector(`#${urlTypes[i]}`)) {
        document.body.append(selectionItem);
        return;
      }
    }

    // NOTE in case return did not reach
    // it means that there is no link to process
    e.target.removeAttribute('href-data')

    //if (!e.target.getAttribute('was-ref') == '') {
    //  e.target.removeAttribute('href-data')
    //}

  }
});

function createButton(x, y, url) {
  // create element
  let item = document.createElement(namespace);
  // set content
  item.id = namespace;
  // set position
  item.style.all = 'unset';
  item.style.position = 'absolute';
  //item.style.left = x+5 + 'px';
  //item.style.top = y-3 + 'px';
  item.style.left = x+45 + 'px';
  item.style.top = y-65 + 'px';
  // set appearance
  item.style.fontFamily = 'none'; // emoji
  item.style.background = '#333';
  item.style.borderRadius = '5%';
  item.style.padding = '3px';
  item.style.zIndex = 10000;
  //item.style.opacity = 0.7;
  //item.style.filter = 'brightness(0.7) drop-shadow(2px 4px 6px black)'
  item.style.filter = 'brightness(0.7)'
  // center character
  item.style.justifyContent = 'center';
  item.style.alignItems = 'center';
  item.style.display = 'flex';
  // disable selection marks
  item.style.userSelect = 'none';
  item.style.cursor = 'default';
  // set button behaviour
  item.onmouseover = () => {
    //item.style.opacity = 1;
    //item.style.filter = 'drop-shadow(2px 4px 6px black)';
    item.style.filter = 'unset';
  };
  item.onmouseleave = () => { // onmouseout
    // TODO Wait a few seconds
    item.remove();
  };
  return item;
}

function extractURL(url) {
  let item = document.createElement('a');
  item.textContent = '🔗'; // 🧧 🏷️ 🔖
  item.title = 'Extracted URL';
  item.id = 'url-extracted';
  item.style.all = 'unset';
  item.style.outline = 'none';
  item.style.height = '15px';
  item.style.width = '15px';
  item.style.padding = '3px';
  item.style.margin = '3px';
  //item.style.fontSize = '0.9rem' // 90%
  item.style.lineHeight = 'normal'; // initial
  //item.style.height = 'fit-content';
  item.href = url;
  return item;
}

// TODO Use icons (with shapes) for cases when color is not optimal
function purgeURL(url, listType) {
  let orgUrl = null;
  let itemTitle, itemId, resUrl;
  let item = document.createElement('a');
  item.style.all = 'unset';
  switch (listType) {
    case 'blacklist':
      itemColor = 'yellow';
      //itemTextContent = '🟡';
      itemTitle = 'Clean link'; // Purged URL
      itemId = 'url-purged';
      resUrl = hrefDataHandler(url, blacklist);
      break;
    case 'original': // TODO dbclick (double-click)
      itemColor = 'orangered';
      //itemTextContent = '🔴';
      itemTitle = 'Unsafe link'; // Original URL
      itemId = 'url-original';
      //resUrl = encodeURI(url);
      // NOTE By executing url.searchParams.sort()
      // we change the order of parameters
      // which means that we create a new and unique url
      // which means that it can be used to identify users that use this program
      // NOTE We execute url.searchParams.sort()
      // in order to avoid false positive for url of blacklisted parameters
      // but we don't apply that change on item "url-original"
      //url.searchParams.sort();
      orgUrl = url;
      item.style.cursor = `not-allowed`; // no-drop
      item.onmouseenter = () => {
        item.style.filter = `drop-shadow(2px 4px 6px ${itemColor})`;
      };
      item.onmouseout = () => {
        item.style.filter = 'unset';
      };
      break;
    case 'whitelist':
      itemColor = 'lawngreen';
      //itemTextContent = '🟢';
      itemTitle = 'Safe link'; // Link with whitelisted parameters
      itemId = 'url-known';
      resUrl = hrefDataHandler(url, whitelist);
      break;
    default:
      itemColor = 'antiquewhite';
      //itemTextContent = '⚪';
      itemTitle = 'Base link'; // Link without parameters
      itemId = 'url-base';
      resUrl = url.origin + url.pathname;
      resUrl = new URL(resUrl); // NOTE To avoid error in resUrl.searchParams.sort()
      break;
  }
  item.id = itemId;
  item.title = itemTitle;
  item.style.background = itemColor;
  //item.textContent = itemTextContent;
  item.style.borderRadius = '50%';
  item.style.outline = 'none';
  item.style.height = '15px';
  item.style.width = '15px';
  item.style.padding = '3px';
  item.style.margin = '3px';
  if (orgUrl){
    item.href = orgUrl;
  } else {
    // NOTE Avoid duplicates by sorting parameters of all links
    resUrl.searchParams.sort();
    // NOTE Avoid false positive by decoding
    // TODO decode from ?C=N%3BO%3DD to ?C=N;O=D
    // FIXME decodeURI doesn't appear to work
    // Text page https://mirror.lyrahosting.com/gnu/a2ps/ (columns raw)
    // SOLVED See "urlsToCompare"
    //item.href = decodeURI(resUrl);
    item.href = resUrl;
  }
  return item;
}

// NOTE The URL API doesn't list parameters
// without explicitly calling them, therefore
// reading lengths of unknown parameters is
// impossible, hence
// set a loop from Aa - Zz or
// add Aa - Zz to whitelist
function hrefDataHandler(url, listType) {
  url = new URL(url.href);
  // NOTE Avoid duplicates by sorting parameters of all links
  //url.searchParams.sort();
  switch (listType) {
    case whitelist:
      let newURL = new URL (url.origin + url.pathname);
      for (let i = 0; i < whitelist.length; i++) {
        if (url.searchParams.get(whitelist[i])) {
          newURL.searchParams.set(
            whitelist[i],
            url.searchParams.get(whitelist[i]) // catchedValue
          );
        }
      }

      // Whitelist parameters of single character long
      let a2z = alphabet.split('');
      for (let i = 0; i < a2z.length; i++) {
        if (url.searchParams.get(a2z[i])) {
          newURL.searchParams.set(
            a2z[i],
            url.searchParams.get(a2z[i])
          );
        }
      }

      let A2Z = alphabet.toUpperCase().split('');
      for (let i = 0; i < A2Z.length; i++) {
        if (url.searchParams.get(A2Z[i])) {
          newURL.searchParams.set(
            A2Z[i],
            url.searchParams.get(A2Z[i])
          );
        }
      }

      /*
      let a2z = genCharArray('a', 'z');
      for (let i = 0; i < a2z.length; i++) {
        if (url.searchParams.get(a2z[i])) {
          newURL.searchParams.set(
            a2z[i],
            url.searchParams.get(a2z[i]) // catchedValue
          );
        }
      }

      let A2Z = genCharArray('A', 'Z');
      for (let i = 0; i < A2Z.length; i++) {
        if (url.searchParams.get(A2Z[i])) {
          newURL.searchParams.set(
            A2Z[i],
            url.searchParams.get(A2Z[i]) // catchedValue
          );
        }
      }
      */

      url = newURL;
      break;
    case blacklist:
      for (let i = 0; i < blacklist.length; i++) {
        if (url.searchParams.get(blacklist[i])) {
          url.searchParams.delete(blacklist[i]);
          //increaseByOne('parameters')
        }
      }
      //increaseByOne('links')
      break;
  }
  return url;
}

// /questions/24597634/how-to-generate-an-array-of-the-alphabet
function genCharArray(charA, charZ) {
  var a = [], i = charA.charCodeAt(0), j = charZ.charCodeAt(0);
  for (; i <= j; ++i) {
    a.push(String.fromCharCode(i));
  }
  return a;
}

async function increaseByOne(key) {
  let currentValue = await GM.getValue(key, 0)
  await GM.setValue(key, currentValue + 1)
  console.log(key)
  console.log(currentValue)
}

// NOTE Marketplace website which uses /ref= instead of ?ref=
function deleteSerialNumber(url) {
  let newURL = [];
  let pattern = /^[0-9\-]+$/;
  oldURL = url.toString().split('/');
  for (const cell of oldURL) {
    if (!pattern.test(cell)) {
      newURL.push(cell);
    }
  }
  newURL = newURL.join('/');
  return new URL(newURL);
}

QingJ © 2025

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