Clean URL Improved

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

当前为 2023-07-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Clean URL Improved
  3. // @namespace i2p.schimon.cleanurl
  4. // @description Remove tracking parameters and redirect to original URL. This Userscript uses the URL Interface instead of RegEx.
  5. // @homepageURL https://openuserjs.org/scripts/sjehuda/Clean_URL_Improved
  6. // @supportURL https://openuserjs.org/scripts/sjehuda/Clean_URL_Improved/issues
  7. // @copyright 2023, Schimon Jehudah (http://schimon.i2p)
  8. // @license MIT; https://opensource.org/licenses/MIT
  9. // @grant none
  10. // @run-at document-end
  11. // @match file://*
  12. // @match *://*/*
  13. // @version 23.07.08
  14. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn5qlPC90ZXh0Pjwvc3ZnPgo=
  15. // ==/UserScript==
  16.  
  17. // @grant GM.setValue
  18. // @grant GM.getValue
  19.  
  20. /*
  21.  
  22. NEWS
  23.  
  24. Handle /ref=
  25.  
  26. Added .replace(/\/ref=/g, '/?ref=')
  27.  
  28. Coding like a Japanese
  29.  
  30. TODO
  31.  
  32. Delete empty parameters
  33.  
  34. Statistics
  35. GM.getValue('links-total')
  36. GM.getValue('links-bad')
  37. GM.getValue('links-good')
  38. GM.getValue('parameters-total')
  39. GM.getValue('parameters-bad')
  40. GM.getValue('parameters-good')
  41. GM.getValue('parameters-urls')
  42. GM.getValue('parameters-unclassified')
  43.  
  44. GM.setValue('pbl', 'hostname-parameter')
  45. GM.setValue('pwl', 'hostname-parameter')
  46.  
  47. FIXME
  48.  
  49. Whitelisted parameters without values are realized to purged-url
  50.  
  51. */
  52.  
  53. /*
  54.  
  55. Simple version of this Userscript
  56. let url = new URL(location.href);
  57. if (url.hash || url.search) {
  58. location.href = url.origin + url.pathname
  59. };
  60.  
  61. */
  62.  
  63. // https://openuserjs.org/scripts/tfr/YouTube_Link_Cleaner
  64.  
  65. // Check whether HTML; otherwise, exit.
  66. //if (!document.contentType == 'text/html')
  67. if (document.doctype == null) return;
  68.  
  69. //let point = [];
  70. const namespace = 'i2p.schimon.cleanurl';
  71.  
  72. // List of url parameters
  73. const urls = [
  74. 'ap_id',
  75. 'redirect',
  76. 'ref',
  77. 'source',
  78. 'src',
  79. 'url',
  80. 'utm_source',
  81. 'utm_term'];
  82.  
  83. // List of alphabet
  84. const alphabet = 'abcdefghijklmnopqrstuvwxyz';
  85.  
  86. // List of reserved parameters
  87. const whitelist = [
  88. 'act', // invision board
  89. 'activeTab', // npmjs
  90. 'art', // article
  91. 'artist', // bandcamp jamendo
  92. 'action', // bugzilla
  93. 'author', // git
  94. 'ap_id', // activitypub
  95. 'bill', // law
  96. 'board', // simple machines
  97. 'category', // id
  98. 'categories', // searxng
  99. 'catid', // id
  100. 'code', // code
  101. 'component', // addons.palemoon.org
  102. 'content', // id
  103. 'CurrentMDW', // menuetos.be
  104. 'dark', // yorik.uncreated.net
  105. 'date', // date
  106. 'days', // wiki
  107. 'diff', // wiki
  108. 'do_union', // bugzilla
  109. 'district', // house.mo.gov
  110. 'exp_time', // cdn
  111. 'expires', // cdn
  112. 'ezimgfmt', // cdn image processor
  113. 'feedformat', // wiki
  114. 'fid', // mybb
  115. 'file_host', // cdn
  116. 'filename', // filename
  117. 'for', // cdn
  118. 'format', // file type
  119. 'from', // redmine
  120. 'guid', // guid
  121. 'group', // bugzilla
  122. 'group_id', // sourceforge
  123. 'hash', // cdn
  124. 'hidebots', // wiki
  125. 'hl', // language
  126. 'id', // id
  127. 'ie', // character encoding
  128. 'ip', // ip address
  129. 'item_class', // greasyfork
  130. 'item_id', // greasyfork
  131. 'iv_load_policy', // invidious
  132. 'jid', // jabber id (xmpp)
  133. 'key', // cdn
  134. 'lang', // language
  135. 'language', // searxng
  136. 'library', // oujs
  137. 'limit', // wiki
  138. 'locale', // locale
  139. 'logout', // bugzilla
  140. 'lr', // cdn
  141. 'lra', // cdn
  142. 'member', // xmb forum
  143. 'name', // archlinux
  144. 'mobileaction', // wiki
  145. 'news_id', // post
  146. 'oldid', // wiki
  147. 'order', // bugzilla
  148. 'orderBy', // oujs
  149. 'orderDir', // oujs
  150. //'p', // search query / page number
  151. 'page', // mybb
  152. 'pid', // fluxbb
  153. 'preferencesReturnUrl', // return url
  154. 'product', // bugzilla
  155. 'profile', // copy.sh/v86/
  156. //'q', // search query
  157. 'query', // search query
  158. 'query_format', // bugzilla
  159. 'redlink', // wiki
  160. //'referer', // signin NOTE provided pathname contains login (log-in) or signin (sign-in)
  161. 'resolution', // bugzilla
  162. 'requestee', // bugzilla
  163. 'requester', // bugzilla
  164. 'return_to', // signin
  165. //'s', // search query
  166. 'search', // search query
  167. 'showtopic', // invision board
  168. 'show_all_versions', // greasyfork
  169. 'sign', // cdn
  170. 'signature', // cdn
  171. 'sort', // greasyfork
  172. 'speed', // cdn
  173. 'st', // invision board
  174. 'start_time', // media playback
  175. 'state', // cdn
  176. '__switch_theme', // theme (theanarchistlibrary.org)
  177. 'tag', // id
  178. 'template', // zapier
  179. 'tid', // mybb
  180. 'title', // send (share) links and wiki
  181. 'topic', // simple machines
  182. 'type', // file type
  183. //'url', // url NOTE not sure whether to whitelist or blacklist
  184. 'utf8', // encoding
  185. 'urlversion', // wiki
  186. 'version', // greasyfork
  187. //'view', // invision board
  188. //'_x_tr_sl', // translate online service
  189. //'_x_tr_tl=', // translate online service
  190. //'_x_tr_hl=', // translate online service
  191. //'_x_tr_pto', // translate online service
  192. //'_x_tr_hist', // translate online service
  193. 'year' // year
  194. ];
  195.  
  196. // List of useless hash
  197. const hash = [
  198. 'back-url',
  199. 'intcid',
  200. 'niche-',
  201. //'searchinput',
  202. 'src'];
  203.  
  204. // List of useless parameters
  205. const blacklist = [
  206. 'ad',
  207. 'ad_medium',
  208. 'ad_name',
  209. 'ad_pvid',
  210. 'ad_sub',
  211. //'ad_tags',
  212. 'advertising-id',
  213. //'aem_p4p_detail',
  214. 'af',
  215. 'aff',
  216. 'aff_fcid',
  217. 'aff_fsk',
  218. 'aff_platform',
  219. 'aff_trace_key',
  220. 'affparams',
  221. 'afSmartRedirect',
  222. 'afftrack',
  223. 'affparams',
  224. //'aid',
  225. 'algo_exp_id',
  226. 'algo_pvid',
  227. 'ar',
  228. //'ascsubtag',
  229. //'asc_contentid',
  230. 'asgtbndr',
  231. 'atc',
  232. 'ats',
  233. 'autostart',
  234. //'b64e', // breaks yandex
  235. 'bizType',
  236. //'block',
  237. 'bta',
  238. 'businessType',
  239. 'campaign',
  240. 'campaignId',
  241. //'__cf_chl_rt_tk',
  242. //'cid', // breaks sacred magick
  243. 'ck',
  244. //'clickid',
  245. //'client_id',
  246. //'cm_ven',
  247. 'content-id',
  248. 'crid',
  249. 'cst',
  250. 'cts',
  251. 'curPageLogUid',
  252. //'data', // breaks yandex
  253. //'dchild',
  254. //'dclid',
  255. 'deals-widget',
  256. 'dgcid',
  257. 'dicbo',
  258. //'dt',
  259. 'edd',
  260. 'edm_click_module',
  261. //'ei',
  262. //'embed',
  263. '_encoding',
  264. //'etext', // breaks yandex
  265. 'eventSource',
  266. 'fbclid',
  267. 'feature',
  268. 'forced_click',
  269. //'fr',
  270. 'frs',
  271. //'from', // breaks yandex
  272. '_ga',
  273. 'ga_order',
  274. 'ga_search_query',
  275. 'ga_search_type',
  276. 'ga_view_type',
  277. 'gatewayAdapt',
  278. //'gclid',
  279. //'gclsrc',
  280. 'gh_jid',
  281. 'gps-id',
  282. //'gs_lcp',
  283. 'gt',
  284. 'guccounter',
  285. 'hdtime',
  286. 'ICID',
  287. 'ico',
  288. 'ig_rid',
  289. //'idzone',
  290. //'iflsig',
  291. 'intcmp',
  292. 'irclickid',
  293. //'irgwc',
  294. //'irpid',
  295. 'itid',
  296. //'itok',
  297. //'katds_labels',
  298. //'keywords',
  299. 'keyno',
  300. 'l10n',
  301. 'linkCode',
  302. 'mc',
  303. 'mid',
  304. '__mk_de_DE',
  305. 'mp',
  306. 'nats',
  307. 'nci',
  308. 'obOrigUrl',
  309. 'offer_id',
  310. 'optout',
  311. 'oq',
  312. 'organic_search_click',
  313. 'pa',
  314. 'Partner',
  315. 'partner',
  316. 'partner_id',
  317. 'pcampaignid',
  318. 'pd_rd_i',
  319. 'pd_rd_r',
  320. 'pd_rd_w',
  321. 'pd_rd_wg',
  322. 'pdp_npi',
  323. 'pf_rd_i',
  324. 'pf_rd_m',
  325. 'pf_rd_p',
  326. 'pf_rd_r',
  327. 'pf_rd_s',
  328. 'pf_rd_t',
  329. 'pg',
  330. 'PHPSESSID',
  331. 'pk_campaign',
  332. 'pdp_ext_f',
  333. 'pkey',
  334. 'platform',
  335. 'plkey',
  336. 'pqr',
  337. 'pr',
  338. 'pro',
  339. 'prod',
  340. 'prom',
  341. 'promo',
  342. 'promocode',
  343. 'promoid',
  344. 'psc',
  345. 'psprogram',
  346. 'pvid',
  347. 'qid',
  348. //'r',
  349. 'realDomain',
  350. 'recruiter_id',
  351. 'redirect',
  352. 'ref',
  353. 'ref_',
  354. 'ref_src',
  355. 'refcode',
  356. 'referrer',
  357. 'refinements',
  358. 'reftag',
  359. 'rnid',
  360. 'rowan_id1',
  361. 'rowan_msg_id',
  362. //'rss',
  363. //'sCh',
  364. 'sclient',
  365. 'scm',
  366. 'scm_id',
  367. 'scm-url',
  368. //'sd',
  369. 'sh',
  370. 'shareId',
  371. 'showVariations',
  372. 'si',
  373. //'sid', // breaks whatsup.org.il
  374. '___SID',
  375. //'site_id',
  376. 'sk',
  377. 'smid',
  378. 'social_params',
  379. 'source',
  380. 'sourceId',
  381. 'sp_csd',
  382. 'spLa',
  383. 'spm',
  384. 'spreadType',
  385. //'sprefix',
  386. 'sr',
  387. 'src',
  388. '_src',
  389. 'src_cmp',
  390. 'src_player',
  391. 'src_src',
  392. 'srcSns',
  393. 'su',
  394. //'sxin_0_pb',
  395. '_t',
  396. //'tag',
  397. 'tcampaign',
  398. 'td',
  399. 'terminal_id',
  400. //'text',
  401. 'th', // Sometimes restored after page load
  402. //'title',
  403. 'tracelog',
  404. 'traffic_id',
  405. 'traffic_type',
  406. 'tt',
  407. 'uact',
  408. 'ug_edm_item_id',
  409. 'utm',
  410. //'utm1',
  411. //'utm2',
  412. //'utm3',
  413. //'utm4',
  414. //'utm5',
  415. //'utm6',
  416. //'utm7',
  417. //'utm8',
  418. //'utm9',
  419. 'utm_campaign',
  420. 'utm_content',
  421. 'utm_medium',
  422. 'utm_source',
  423. 'utm_term',
  424. 'uuid',
  425. //'utype',
  426. //'ve',
  427. //'ved',
  428. //'zone'
  429. ];
  430.  
  431. // URL Indexers
  432. const paraIDX = [
  433. 'algo_exp_id',
  434. 'algo_pvid',
  435. 'b64e',
  436. 'cst',
  437. 'cts',
  438. 'data',
  439. 'ei',
  440. //'etext',
  441. 'from',
  442. 'iflsig',
  443. 'gbv',
  444. 'gs_lcp',
  445. 'hdtime',
  446. 'keyno',
  447. 'l10n',
  448. 'mc',
  449. 'oq',
  450. //'q',
  451. 'sei',
  452. 'sclient',
  453. 'sign',
  454. 'source',
  455. 'state',
  456. //'text',
  457. 'uact',
  458. 'uuid',
  459. 'ved'];
  460.  
  461. // Market Places
  462. const paraMKT = [
  463. '___SID',
  464. '_t',
  465. 'ad_pvid',
  466. 'af',
  467. 'aff_fsk',
  468. 'aff_platform',
  469. 'aff_trace_key',
  470. 'afSmartRedirect',
  471. 'bizType',
  472. 'businessType',
  473. 'ck',
  474. 'content-id',
  475. 'crid',
  476. 'curPageLogUid',
  477. 'deals-widget',
  478. 'edm_click_module',
  479. 'gatewayAdapt',
  480. 'gps-id',
  481. 'keywords',
  482. '__mk_de_DE',
  483. 'pd_rd_i',
  484. 'pd_rd_r',
  485. 'pd_rd_w',
  486. 'pd_rd_wg',
  487. 'pdp_npi',
  488. 'pf_rd_i',
  489. 'pf_rd_m',
  490. 'pf_rd_p',
  491. 'pf_rd_r',
  492. 'pf_rd_s',
  493. 'pf_rd_t',
  494. 'platform',
  495. 'pdp_ext_f',
  496. 'ref_',
  497. 'refinements',
  498. 'rnid',
  499. 'rowan_id1',
  500. 'rowan_msg_id',
  501. 'scm',
  502. 'scm_id',
  503. 'scm-url',
  504. 'shareId',
  505. //'showVariations',
  506. 'sk',
  507. 'smid',
  508. 'social_params',
  509. 'spLa',
  510. 'spm',
  511. 'spreadType',
  512. 'sr',
  513. 'srcSns',
  514. //'sxin_0_pb',
  515. 'terminal_id',
  516. 'th', // Sometimes restored after page load
  517. 'tracelog',
  518. 'tt',
  519. 'ug_edm_item_id'];
  520.  
  521. // IL
  522. const paraIL = [
  523. 'dicbo',
  524. 'obOrigUrl'];
  525.  
  526. // General
  527. const paraWWW = [
  528. 'aff',
  529. 'promo',
  530. 'promoid',
  531. 'ref',
  532. 'utm_campaign',
  533. 'utm_content',
  534. 'utm_medium',
  535. 'utm_source',
  536. 'utm_term'];
  537.  
  538. // For URL of the Address bar
  539. // Check and modify page address
  540. // TODO Add bar and ask to clean address bar
  541. (function modifyURL() {
  542.  
  543. let
  544. check = [],
  545. // NOTE Marketplace website which uses /ref= instead of ?ref=
  546. // location.href.replace(/\/ref=/g, '/?ref=');
  547. url = new URL(location.href.replace('/ref=', '/?ref='));
  548.  
  549. // TODO turn into boolean function
  550. for (let i = 0; i < blacklist.length; i++) {
  551. if (url.searchParams.get(blacklist[i])) {
  552. check.push(blacklist[i]);
  553. url.searchParams.delete(blacklist[i]);
  554. //newURL = url.origin + url.pathname + url.search + url.hash;
  555. }
  556. }
  557.  
  558. // TODO turn into boolean function
  559. for (let i = 0; i < hash.length; i++) {
  560. if (url.hash.startsWith('#' + hash[i])) {
  561. check.push(hash[i]);
  562. //newURL = url.origin + url.pathname + url.search;
  563. }
  564. }
  565.  
  566. if (check.length > 0) {
  567. let newURL = url.origin + url.pathname + url.search;
  568. window.history.pushState(null, null, newURL);
  569. //location.href = newURL;
  570. }
  571.  
  572. })();
  573.  
  574. // NOTE Marketplace website which uses /ref= instead of ?ref=
  575. (function correctSlashRefURLs() {
  576. for (let i = 0; i < document.links.length; i++) {
  577. if (document.links[i].href.includes('/ref=')) {
  578. document.links[i].href = document.links[i].href.replace('/ref=', '/?ref=');
  579. document.links[i].setAttribute('was-ref', '');
  580. }
  581. }
  582. })();
  583.  
  584. (function scanAllURLs() {
  585. for (let i = 0; i < document.links.length; i++) {
  586. let url = new URL(document.links[i].href);
  587. // NOTE Consider BitTorrent Magnet links
  588. // removing trackers would need a warning about
  589. // private torrents, if torrent is not public (dht-enabled)
  590. const allowedProtocols = [
  591. 'finger:', 'freenet:', 'gemini:',
  592. 'gopher:', 'wap:', 'ipfs:',
  593. 'https:', 'ftps:', 'http:', 'ftp:'];
  594. if (url.search && allowedProtocols.includes(url.protocol)) {
  595. //if (url.search || url.hash) {
  596. document.links[i].setAttribute('href-data', document.links[i].href);
  597. }
  598. }
  599. })();
  600.  
  601. (function scanBadURLs() {
  602. for (let i = 0; i < document.links.length; i++) {
  603. // TODO callback, Mutation Observer, and Event Listener
  604. // TODO Count links increaseByOne('links')
  605. // NOTE To count links, add return statement to function cleanLink()
  606. // return statement will indicate that link is positive for subject
  607. // parameters and therefore should be counted. Counter will be added
  608. // by one, once detected that url is not equal to (new) url.
  609. hash.forEach(j => cleanLink(document.links[i], j, 'hash'));
  610. blacklist.forEach(j => cleanLink(document.links[i], j, 'para'));
  611. }
  612. })();
  613.  
  614. // TODO Add an Event Listener
  615. function cleanLink(link, target, type) {
  616. let url = new URL(link.href);
  617. switch (type) {
  618. case 'hash':
  619. //console.log('hash ' + i)
  620. if (url.hash.startsWith('#' + target)) {
  621. //link.setAttribute('href-data', link.href);
  622. link.href = url.origin + url.pathname + url.search;
  623. //increaseByOne('hashes')
  624. }
  625. break;
  626. case 'para':
  627. //console.log('para ' + i)
  628. if (url.searchParams.get(target)) {
  629. url.searchParams.delete(target);
  630. //link.setAttribute('href-data', link.href);
  631. link.href = url.origin + url.pathname + url.search;
  632. //increaseByOne('parameters')
  633. }
  634. break;
  635. }
  636.  
  637. /*
  638. // EXTRA
  639. // For URL of hyperlinks
  640. for (const a of document.querySelectorAll('a')) {
  641. try{
  642. let url = new URL(a.href);
  643. for (let i = 0; i < blacklist.length; i++) {
  644. if (url.searchParams.get(blacklist[i])) {
  645. url.searchParams.delete(blacklist[i]);
  646. }
  647. }
  648. a.href = url;
  649. } catch (err) {
  650. //console.warn('Found no href for element: ' + a);
  651. //console.error(err);
  652. }
  653. } */
  654.  
  655. }
  656.  
  657. // TODO Hunt (for any) links within attributes using getAttributeNames()[i]
  658.  
  659. // Event Listener
  660. // TODO Scan 'e.target.childNodes' until 'href-data' (link) is found
  661. document.body.addEventListener("mouseover", function(e) { // mouseover works with keyboard too
  662. //if (e.target && e.target.nodeName == "A") {
  663. let hrefData = e.target.getAttribute('href-data');
  664. //if (e.target && hrefData && !document.getElementById(namespace)) {
  665. if (e.target && hrefData && hrefData != document.getElementById('url-original')) {
  666. if (document.getElementById(namespace)) {
  667. document.getElementById(namespace).remove();
  668. }
  669. selectionItem = createButton(e.pageX, e.pageY, hrefData);
  670. hrefData = new URL(hrefData);
  671. selectionItem.append(purgeURL(hrefData));
  672. let types = ['whitelist', 'blacklist', 'original']
  673. for (let i = 0; i < types.length; i++) {
  674. let button = purgeURL(hrefData, types[i]);
  675. if (types[i] == 'original' && e.target.getAttribute('was-ref') == '') {
  676. button.href = button.href.replace('/?ref=', '/ref=');
  677. }
  678. let exist;
  679. selectionItem.childNodes.forEach(
  680. node => {
  681. if (button.href == node.href) {
  682. exist = true;
  683. }
  684. }
  685. )
  686. if (!exist) {
  687. selectionItem.append(button);
  688. }
  689. }
  690.  
  691. // Check for URLs
  692. for (let i = 0; i < urls.length; i++) {
  693. if (hrefData.searchParams.get(urls[i])) { // hrefData.includes('url=')
  694. urlParameter = hrefData.searchParams.get(urls[i]);
  695. try {
  696. urlParameter = new URL (urlParameter);
  697. } catch {
  698. if (urlParameter.includes('.')) { // NOTE It is a guess
  699. try {
  700. urlParameter = new URL ('http:' + urlParameter);
  701. } catch {}
  702. }
  703. }
  704. if (typeof urlParameter == 'object' && // confirm url object
  705. urlParameter != location.href) { // provided url isn't the same as of page
  706. newURLItem = extractURL(urlParameter);
  707. selectionItem.prepend(newURLItem);
  708. }
  709. }
  710. }
  711.  
  712. /*
  713. // compare original against purged
  714. //if (selectionItem.querySelector(`#url-purged`) &&
  715. // selectionItem.querySelector(`#url-original`)) {
  716. if (selectionItem.querySelector(`#url-purged`)) {
  717. //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
  718. let urlPurge = new URL (selectionItem.querySelector(`#url-purged`).href);
  719. // NOTE
  720. // These "searchParams.sort" ~~may be~~ *are not* redundant.
  721. // See resUrl.searchParams.sort()
  722. urlPurge.searchParams.sort();
  723. hrefData.searchParams.sort();
  724. //console.log(hrefData.search);
  725. //console.log(urlPurge.search);
  726. if (hrefData.search == urlPurge.search &&
  727. selectionItem.querySelector(`#url-original`)) {
  728. selectionItem.querySelector(`#url-original`).remove();
  729. }
  730. } else
  731. // compare original against safe
  732. if (selectionItem.querySelector(`#url-known`)) {
  733. //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
  734. let urlKnown = new URL (selectionItem.querySelector(`#url-known`).href);
  735. // NOTE
  736. // These "searchParams.sort" ~~may be~~ *are not* redundant.
  737. // See resUrl.searchParams.sort()
  738. urlKnown.searchParams.sort();
  739. hrefData.searchParams.sort();
  740. //console.log(hrefData.search);
  741. //console.log(urlKnown.search);
  742. if (hrefData.search == urlKnown.search &&
  743. selectionItem.querySelector(`#url-original`)) {
  744. selectionItem.querySelector(`#url-original`).remove();
  745. }
  746. }
  747. */
  748.  
  749. // compare original against safe and purged
  750. // NOTE on "item.href = decodeURI(resUrl)"
  751. // The solution was here.
  752. // Decode was not the issue
  753. // This is a good example to show that
  754. // smaller tasks are as important as bigger tasks
  755. let urlsToCompare = ['#url-known', '#url-purged'];
  756. for (let i = 0; i < urlsToCompare.length; i++) {
  757. if (selectionItem.querySelector(urlsToCompare[i])) {
  758. //let urlOrigin = new URL (selectionItem.querySelector(`#url-original`).href);
  759. let urlToCompare = new URL (selectionItem.querySelector(urlsToCompare[i]).href);
  760. // NOTE
  761. // These "searchParams.sort" ~~may be~~ *are not* redundant.
  762. // See resUrl.searchParams.sort()
  763. urlToCompare.searchParams.sort();
  764. hrefData.searchParams.sort();
  765. //console.log(hrefData.search);
  766. //console.log(urlToCompare.search);
  767. if (hrefData.search == urlToCompare.search &&
  768. selectionItem.querySelector(`#url-original`)) {
  769. selectionItem.querySelector(`#url-original`).remove();
  770. }
  771. }
  772. }
  773.  
  774. // do not add element, if url has only whitelisted parameters and no potential url
  775. // add element, only if a potential url or non-whitelisted parameter was found
  776. let urlTypes = ['url-extracted', 'url-original', 'url-purged'];
  777. for (let i = 0; i < urlTypes.length; i++) {
  778. if (selectionItem.querySelector(`#${urlTypes[i]}`)) {
  779. document.body.append(selectionItem);
  780. return;
  781. }
  782. }
  783.  
  784. // NOTE in case return did not reach
  785. // it means that there is no link to process
  786. e.target.removeAttribute('href-data')
  787.  
  788. //if (!e.target.getAttribute('was-ref') == '') {
  789. // e.target.removeAttribute('href-data')
  790. //}
  791.  
  792. }
  793. });
  794.  
  795. function createButton(x, y, url) {
  796. // create element
  797. let item = document.createElement(namespace);
  798. // set content
  799. item.id = namespace;
  800. // set position
  801. item.style.all = 'unset';
  802. item.style.position = 'absolute';
  803. //item.style.left = x+5 + 'px';
  804. //item.style.top = y-3 + 'px';
  805. item.style.left = x+45 + 'px';
  806. item.style.top = y-65 + 'px';
  807. // set appearance
  808. item.style.fontFamily = 'none'; // emoji
  809. item.style.background = '#333';
  810. item.style.borderRadius = '5%';
  811. item.style.padding = '3px';
  812. item.style.zIndex = 10000;
  813. //item.style.opacity = 0.7;
  814. //item.style.filter = 'brightness(0.7) drop-shadow(2px 4px 6px black)'
  815. item.style.filter = 'brightness(0.7)'
  816. // center character
  817. item.style.justifyContent = 'center';
  818. item.style.alignItems = 'center';
  819. item.style.display = 'flex';
  820. // disable selection marks
  821. item.style.userSelect = 'none';
  822. item.style.cursor = 'default';
  823. // set button behaviour
  824. item.onmouseover = () => {
  825. //item.style.opacity = 1;
  826. //item.style.filter = 'drop-shadow(2px 4px 6px black)';
  827. item.style.filter = 'unset';
  828. };
  829. item.onmouseleave = () => { // onmouseout
  830. // TODO Wait a few seconds
  831. item.remove();
  832. };
  833. return item;
  834. }
  835.  
  836. function extractURL(url) {
  837. let item = document.createElement('a');
  838. item.textContent = '🔗'; // 🧧 🏷️ 🔖
  839. item.title = 'Extracted URL';
  840. item.id = 'url-extracted';
  841. item.style.all = 'unset';
  842. item.style.outline = 'none';
  843. item.style.height = '15px';
  844. item.style.width = '15px';
  845. item.style.padding = '3px';
  846. item.style.margin = '3px';
  847. //item.style.fontSize = '0.9rem' // 90%
  848. item.style.lineHeight = 'normal'; // initial
  849. //item.style.height = 'fit-content';
  850. item.href = url;
  851. return item;
  852. }
  853.  
  854. // TODO Use icons (with shapes) for cases when color is not optimal
  855. function purgeURL(url, listType) {
  856. let orgUrl = null;
  857. let itemTitle, itemId, resUrl;
  858. let item = document.createElement('a');
  859. item.style.all = 'unset';
  860. switch (listType) {
  861. case 'blacklist':
  862. itemColor = 'yellow';
  863. //itemTextContent = '🟡';
  864. itemTitle = 'Clean link'; // Purged URL
  865. itemId = 'url-purged';
  866. resUrl = hrefDataHandler(url, blacklist);
  867. break;
  868. case 'original': // TODO dbclick (double-click)
  869. itemColor = 'orangered';
  870. //itemTextContent = '🔴';
  871. itemTitle = 'Unsafe link'; // Original URL
  872. itemId = 'url-original';
  873. //resUrl = encodeURI(url);
  874. // NOTE By executing url.searchParams.sort()
  875. // we change the order of parameters
  876. // which means that we create a new and unique url
  877. // which means that it can be used to identify users that use this program
  878. // NOTE We execute url.searchParams.sort()
  879. // in order to avoid false positive for url of blacklisted parameters
  880. // but we don't apply that change on item "url-original"
  881. //url.searchParams.sort();
  882. orgUrl = url;
  883. item.style.cursor = `not-allowed`; // no-drop
  884. item.onmouseenter = () => {
  885. item.style.filter = `drop-shadow(2px 4px 6px ${itemColor})`;
  886. };
  887. item.onmouseout = () => {
  888. item.style.filter = 'unset';
  889. };
  890. break;
  891. case 'whitelist':
  892. itemColor = 'lawngreen';
  893. //itemTextContent = '🟢';
  894. itemTitle = 'Safe link'; // Link with whitelisted parameters
  895. itemId = 'url-known';
  896. resUrl = hrefDataHandler(url, whitelist);
  897. break;
  898. default:
  899. itemColor = 'antiquewhite';
  900. //itemTextContent = '⚪';
  901. itemTitle = 'Base link'; // Link without parameters
  902. itemId = 'url-base';
  903. resUrl = url.origin + url.pathname;
  904. resUrl = new URL(resUrl); // NOTE To avoid error in resUrl.searchParams.sort()
  905. break;
  906. }
  907. item.id = itemId;
  908. item.title = itemTitle;
  909. item.style.background = itemColor;
  910. //item.textContent = itemTextContent;
  911. item.style.borderRadius = '50%';
  912. item.style.outline = 'none';
  913. item.style.height = '15px';
  914. item.style.width = '15px';
  915. item.style.padding = '3px';
  916. item.style.margin = '3px';
  917. if (orgUrl){
  918. item.href = orgUrl;
  919. } else {
  920. // NOTE Avoid duplicates by sorting parameters of all links
  921. resUrl.searchParams.sort();
  922. // NOTE Avoid false positive by decoding
  923. // TODO decode from ?C=N%3BO%3DD to ?C=N;O=D
  924. // FIXME decodeURI doesn't appear to work
  925. // Text page https://mirror.lyrahosting.com/gnu/a2ps/ (columns raw)
  926. // SOLVED See "urlsToCompare"
  927. //item.href = decodeURI(resUrl);
  928. item.href = resUrl;
  929. }
  930. return item;
  931. }
  932.  
  933. // NOTE The URL API doesn't list parameters
  934. // without explicitly calling them, therefore
  935. // reading lengths of unknown parameters is
  936. // impossible, hence
  937. // set a loop from Aa - Zz or
  938. // add Aa - Zz to whitelist
  939. function hrefDataHandler(url, listType) {
  940. url = new URL(url.href);
  941. // NOTE Avoid duplicates by sorting parameters of all links
  942. //url.searchParams.sort();
  943. switch (listType) {
  944. case whitelist:
  945. let newURL = new URL (url.origin + url.pathname);
  946. for (let i = 0; i < whitelist.length; i++) {
  947. if (url.searchParams.get(whitelist[i])) {
  948. newURL.searchParams.set(
  949. whitelist[i],
  950. url.searchParams.get(whitelist[i]) // catchedValue
  951. );
  952. }
  953. }
  954.  
  955. // Whitelist parameters of single character long
  956. let a2z = alphabet.split('');
  957. for (let i = 0; i < a2z.length; i++) {
  958. if (url.searchParams.get(a2z[i])) {
  959. newURL.searchParams.set(
  960. a2z[i],
  961. url.searchParams.get(a2z[i])
  962. );
  963. }
  964. }
  965.  
  966. let A2Z = alphabet.toUpperCase().split('');
  967. for (let i = 0; i < A2Z.length; i++) {
  968. if (url.searchParams.get(A2Z[i])) {
  969. newURL.searchParams.set(
  970. A2Z[i],
  971. url.searchParams.get(A2Z[i])
  972. );
  973. }
  974. }
  975.  
  976. /*
  977. let a2z = genCharArray('a', 'z');
  978. for (let i = 0; i < a2z.length; i++) {
  979. if (url.searchParams.get(a2z[i])) {
  980. newURL.searchParams.set(
  981. a2z[i],
  982. url.searchParams.get(a2z[i]) // catchedValue
  983. );
  984. }
  985. }
  986.  
  987. let A2Z = genCharArray('A', 'Z');
  988. for (let i = 0; i < A2Z.length; i++) {
  989. if (url.searchParams.get(A2Z[i])) {
  990. newURL.searchParams.set(
  991. A2Z[i],
  992. url.searchParams.get(A2Z[i]) // catchedValue
  993. );
  994. }
  995. }
  996. */
  997.  
  998. url = newURL;
  999. break;
  1000. case blacklist:
  1001. for (let i = 0; i < blacklist.length; i++) {
  1002. if (url.searchParams.get(blacklist[i])) {
  1003. url.searchParams.delete(blacklist[i]);
  1004. //increaseByOne('parameters')
  1005. }
  1006. }
  1007. //increaseByOne('links')
  1008. break;
  1009. }
  1010. return url;
  1011. }
  1012.  
  1013. // /questions/24597634/how-to-generate-an-array-of-the-alphabet
  1014. function genCharArray(charA, charZ) {
  1015. var a = [], i = charA.charCodeAt(0), j = charZ.charCodeAt(0);
  1016. for (; i <= j; ++i) {
  1017. a.push(String.fromCharCode(i));
  1018. }
  1019. return a;
  1020. }
  1021.  
  1022. async function increaseByOne(key) {
  1023. let currentValue = await GM.getValue(key, 0)
  1024. await GM.setValue(key, currentValue + 1)
  1025. console.log(key)
  1026. console.log(currentValue)
  1027. }
  1028.  
  1029. // NOTE Marketplace website which uses /ref= instead of ?ref=
  1030. function deleteSerialNumber(url) {
  1031. let newURL = [];
  1032. let pattern = /^[0-9\-]+$/;
  1033. oldURL = url.toString().split('/');
  1034. for (const cell of oldURL) {
  1035. if (!pattern.test(cell)) {
  1036. newURL.push(cell);
  1037. }
  1038. }
  1039. newURL = newURL.join('/');
  1040. return new URL(newURL);
  1041. }