CleanURLs (Clean URL Improved)

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

目前为 2025-05-06 提交的版本。查看 最新版本

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