Clean URL Improved

Remove tracking parameters and redirect to original URL. This software utilizes the URL Interface instead of RegEx.

当前为 2025-04-09 提交的版本,查看 最新版本

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