CleanURLs

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

目前为 2023-06-24 提交的版本。查看 最新版本

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