CleanURLs

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

目前為 2024-04-28 提交的版本,檢視 最新版本

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