CleanURLs

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

当前为 2023-10-17 提交的版本,查看 最新版本

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