Canonical links

Remove URL redirections added by some social networks. Use HTTPS for other shorteners. Improve privacy.

  1. // ==UserScript==
  2. // @name Canonical links
  3. // @author Guillaume
  4. // @description Remove URL redirections added by some social networks. Use HTTPS for other shorteners. Improve privacy.
  5. // @namespace https://greasyfork.org/fr/users/11386-guillaume
  6. // @include https://twitter.com/*
  7. // @include https://identi.ca/*
  8. // @include https://microca.st/*
  9. // @include https://m.facebook.com/*
  10. // @version 1.5
  11. // @grant none
  12. // @run-at document-start
  13. // @license CC by-sa http://creativecommons.org/licenses/by-sa/3.0/
  14. // ==/UserScript==
  15.  
  16. // Credits:
  17. //
  18. // - getVisibleText, textContentVisible:
  19. // This is derived from a work by Ethan Brown.
  20. // Original license: cc-by sa
  21. // Source: http://stackoverflow.com/questions/19985306/get-the-innertext-of-an-element-but-exclude-hidden-children
  22.  
  23. function getVisibleText( node )
  24. {
  25. if ( node.nodeType === 1 ) // Element
  26. {
  27. var rect = node.getBoundingClientRect();
  28. if ( rect.width === 0 || rect.height === 0 )
  29. return '';
  30. }
  31. else if ( node.nodeType === 3 ) // Text
  32. {
  33. return node.textContent;
  34. }
  35. var text = '';
  36. for( var i = 0; i < node.childNodes.length; i++ )
  37. text += getVisibleText( node.childNodes[i] );
  38. return text;
  39. }
  40.  
  41. if ( ! Node.prototype.hasOwnProperty('textContentVisible') )
  42. {
  43. Object.defineProperty(Node.prototype, 'textContentVisible', {
  44. get: function() {
  45. return getVisibleText( this );
  46. },
  47. enumerable: true
  48. });
  49. }
  50.  
  51. if ( ! HTMLAnchorElement.prototype.hasOwnProperty('getSearchParam') )
  52. {
  53. Object.defineProperty(HTMLAnchorElement.prototype, 'getSearchParam', {
  54. get: function()
  55. {
  56. var a = this;
  57. return function(b)
  58. {
  59. var res = a.search.match('[\?&]' + b + '=([^=]*)(?:&[^;=]*=|$)') || [];
  60. return res[1];
  61. }
  62. },
  63. enumerable: true
  64. });
  65. }
  66.  
  67. var hostRules = {};
  68.  
  69. var applyHostRules = function(el)
  70. {
  71. // normalize host
  72. el.host = el.host;
  73. var i = 0;
  74. var limit = 3;
  75. while ( hostRules[el.hostname] && i < limit )
  76. {
  77. var urlFactory = hostRules[el.hostname];
  78. el = urlFactory(el);
  79. i++;
  80. }
  81. }
  82.  
  83. hostRules['youtu.be'] = function(el)
  84. {
  85. el.href = 'https://www.youtube.com/watch?v=' + el.pathname.substr(1);
  86. return el;
  87. };
  88. hostRules['lm.facebook.com']
  89. = hostRules['m.facebook.com']
  90. = function(el)
  91. {
  92. var uParam = el.getSearchParam('u');
  93. if ( el.pathname == '/l.php' && uParam !== undefined )
  94. {
  95. el.href = unescape(uParam);
  96. }
  97. return el;
  98. };
  99.  
  100. var makeLinkHTTPS = function(el)
  101. {
  102. if ( el.protocol == 'http:' )
  103. el.protocol = 'https:';
  104. return el;
  105. }
  106.  
  107. hostRules = [
  108. 'ask.fm', 'awe.sm', 'bit.ly', 'bc.vc',
  109. 'd.pr', 'db.tt', 'dlvr.it',
  110. 'fb.me',
  111. 'goo.gl', 'grnpc.org',
  112. 'identi.ca', 'is.gd', 'j.mp',
  113. 'lnkd.in', 't.co',
  114. 'tinyurl.com', 'tr.im',
  115. 'v.gd', 'vur.me',
  116. 'wp.me', 'x.co'
  117. ].reduce(function(o, a) { o[a] = makeLinkHTTPS; return o; }, hostRules);
  118.  
  119. var makeLinkExpanded = function(el)
  120. {
  121. var textNode = document.createTextNode(el.textContentVisible);
  122. var url = el.dataset.expandedUrl;
  123. var newLink = document.createElement('a');
  124. newLink.href = url;
  125. newLink.title = el.title;
  126. newLink.className = el.className;
  127. newLink.dir = el.dir;
  128. newLink.appendChild(textNode);
  129. el.parentNode.replaceChild(newLink, el);
  130. };
  131.  
  132. var makeLinkFromTitle = function(el)
  133. {
  134. var url = el.title;
  135. var text = el.textContentVisible;
  136. if ( text.indexOf('http:') === 0 || text.indexOf('https:') )
  137. text = url;
  138. var textNode = document.createTextNode(text);
  139. var newLink = document.createElement('a');
  140. newLink.href = url;
  141. newLink.title = el.title;
  142. newLink.className = el.className;
  143. newLink.dir = el.dir;
  144. newLink.appendChild(textNode);
  145. el.parentNode.replaceChild(newLink, el);
  146. };
  147.  
  148. var onLink = function(el)
  149. {
  150. var updateText = false;
  151. if ( el.childNodes.length == 1
  152. && el.childNodes[0].nodeType === 3
  153. && el.childNodes[0].textContent == el.href )
  154. updateText = true;
  155. applyHostRules(el);
  156. // disable referers
  157. el.rel = 'noreferrer';
  158. if ( updateText )
  159. {
  160. var textNode = document.createTextNode(el.href);
  161. el.replaceChild(textNode, el.childNodes[0]);
  162. }
  163. }
  164.  
  165. var onTwitterLink = function(el)
  166. {
  167. makeLinkExpanded(el);
  168. }
  169.  
  170. var onPumpIOLink = function(el)
  171. {
  172. makeLinkFromTitle(el);
  173. }
  174.  
  175. window.addEventListener("load", function(ev) {
  176. // unshadow for better debugging
  177. delete console.log;
  178. // initial pass
  179. if ( window.Pump )
  180. [].slice.call(document.querySelectorAll('a[title^=http]')).forEach(onPumpIOLink);
  181. [].slice.call(document.querySelectorAll('a[data-expanded-url]')).forEach(onTwitterLink);
  182. [].slice.call(document.querySelectorAll('a[href]')).forEach(onLink);
  183. // watch changes
  184. var observer = new MutationObserver(function(mutations)
  185. {
  186. mutations.forEach(function(mutation)
  187. {
  188. for ( var i = 0; i < mutation.addedNodes.length; i++ )
  189. {
  190. var node = mutation.addedNodes[i];
  191. if ( node.querySelectorAll )
  192. {
  193. [].slice.call(node.querySelectorAll('a[data-expanded-url]')).forEach(onTwitterLink);
  194. [].slice.call(node.querySelectorAll('a[href]')).forEach(onLink);
  195. }
  196. }
  197. });
  198. });
  199.  
  200. var config = { childList: true, subtree: true };
  201. observer.observe(document, config);
  202. }, true);