Clean URL Improved

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

当前为 2023-11-23 提交的版本,查看 最新版本

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