CleanURLs (Clean URL Improved)

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

当前为 2024-09-03 提交的版本,查看 最新版本

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