Clean URL Improved (CleanURLs)

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

当前为 2024-05-21 提交的版本,查看 最新版本

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