Greasy Fork is available in English.

Bovverzoom

View linked images and animated GIFs on Reddit by hovering instead of having to click and navigate away.

  1. // ==UserScript==
  2. // @name Bovverzoom
  3. // @description View linked images and animated GIFs on Reddit by hovering instead of having to click and navigate away.
  4. // @namespace raina
  5. // @include /^https?:\/\/(\w+\.)?reddit\.com\//
  6. // @version 1.9.6
  7. // @done They somehow broke it so I fixed it again, sorry for the wait.
  8. // @done Added support for Reddit hosted GIF animations.
  9. // @done Added respect for NSFW.
  10. // @done Rewrote Reddit hosted image support.
  11. // @done Added support for a newly spotted Giphy link pattern.
  12. // @done Added support for Imgur direct mp4 links.
  13. // @done Made Imgur page links with query parameters work.
  14. // @done Fixed v1.5 addition working only once per image per page load.
  15. // @done Changed Reddit hosted images' thumbnails link from comments to original image to activate them.
  16. // @done Support i.reddituploads.com.
  17. // @done Run on subdomains for languages, no participation mode etc...
  18. // @done Allow passive mixed content, so that images from hosts without HTTPS can load.
  19. // @done Avoid mixed content issues by stripping out explicit protocol. (E.g. GFYCat videos don't load over HTTP from an HTTPS Reddit session).
  20. // @done Handle Imgur ad hoc collection images.
  21. // @done Handle Imgur "gifv" clips.
  22. // @todo Handle DOM mutations.
  23. // @todo Handle Imgur albums and galleries somehow.
  24. // @grant none
  25. // ==/UserScript==
  26. (function() {
  27. "use strict";
  28.  
  29. var i, j;
  30. var doc = document;
  31. var thumbs = [].slice.call(doc.getElementsByClassName("thumbnail"));
  32. var userText = doc.getElementsByClassName("usertext-body");
  33. var show = function() {
  34. this.style.display = "block";
  35. };
  36. var hide = function() {
  37. this.style.display = "none";
  38. };
  39. var img = doc.createElement("img");
  40. img.className = "bovverzoom";
  41. img.cache = "";
  42. img.show = show;
  43. img.hide = hide;
  44.  
  45. var clip = doc.createElement("video");
  46. clip.className = "bovverzoom";
  47. clip.autoplay = "autoplay";
  48. clip.muted = "muted";
  49. clip.loop = "loop";
  50. clip.cache = "";
  51. clip.show = show;
  52. clip.hide = hide;
  53.  
  54. var style = doc.createElement("style");
  55. style.type = "text/css";
  56. style.textContent = ".bovverzoom {" +
  57. "background-color: rgba(0,0,0,.01);" +
  58. "position: fixed;" +
  59. "right: 0;" +
  60. "top: 0;" +
  61. "z-index: 100;" +
  62. "max-width: 100%;" +
  63. "max-height: 100%;" +
  64. "pointer-events: none;" +
  65. "border-radius: 3px;" +
  66. "display: none;" +
  67. "}"+
  68. ".glow {" +
  69. "box-shadow: rgba(255, 255, 255, .5) 0 0 8px" +
  70. "}";
  71.  
  72. var fetchGFY = function(url, e) {
  73. img.hide();
  74. if (url != clip.cache) {
  75. var xhr = new XMLHttpRequest();
  76. xhr.addEventListener("load", showGFY, false);
  77. xhr.open("GET", url, true);
  78. xhr.send();
  79. clip.cache = url;
  80. } else {
  81. clip.show();
  82. }
  83. };
  84.  
  85. var showGFY = function() {
  86. clip.show();
  87. var gfyItem = JSON.parse(this.responseText).gfyItem;
  88. var newClip = clip.cloneNode(false);
  89.  
  90. var webmSrc = doc.createElement("source");
  91. webmSrc.src = gfyItem.webmUrl.replace(/^https?:/, "");
  92. webmSrc.type = "video/webm";
  93. newClip.appendChild(webmSrc);
  94.  
  95. var mp4Src = doc.createElement("source");
  96. mp4Src.src = gfyItem.mp4Url.replace(/^https?:/, "");
  97. mp4Src.type = "video/mp4";
  98. newClip.appendChild(mp4Src);
  99.  
  100. document.body.replaceChild(newClip, clip);
  101. newClip.hide = clip.hide;
  102. newClip.show = clip.show;
  103. clip = newClip;
  104. clip.show();
  105. };
  106.  
  107. var showGIFV = function(url) {
  108. clip.show();
  109. var newClip = clip.cloneNode(false);
  110.  
  111. var webmSrc = doc.createElement("source");
  112. webmSrc.src = url.replace(".gifv", ".webm").replace(/^https?:/, "");
  113. webmSrc.type = "video/webm";
  114. newClip.appendChild(webmSrc);
  115.  
  116. var mp4Src = doc.createElement("source");
  117. mp4Src.src = url.replace(".gifv", ".mp4").replace(/^https?:/, "");
  118. mp4Src.type = "video/mp4";
  119. newClip.appendChild(mp4Src);
  120.  
  121. document.body.replaceChild(newClip, clip);
  122. newClip.hide = clip.hide;
  123. newClip.show = clip.show;
  124. clip = newClip;
  125. clip.show();
  126. };
  127.  
  128. var imgMe = function(src, e) {
  129. clip.hide();
  130. if (src != img.cache) {
  131. img.src = "";
  132. img.src = src;
  133. img.cache = src;
  134. }
  135. img.show();
  136. };
  137.  
  138. var killMe = function(e) {
  139. clip.hide();
  140. img.hide();
  141. };
  142.  
  143. var rigMe = function(src, el) {
  144. el.addEventListener("mouseover", swapper, false);
  145. el.addEventListener("mouseout", swapper, false);
  146. el.className = el.className + " glow";
  147. };
  148.  
  149. var clipFunc = rigMe;
  150. var imgFunc = rigMe;
  151. var swapper = function(e) {
  152. var obj = e;
  153. if ("mouseover" === e.type) {
  154. if (e.target.matches('a')) {
  155. obj = e.target;
  156. } else {
  157. obj = e.target.parentElement;
  158. }
  159. clipFunc = obj.href.match(/gfycat/) ? fetchGFY : showGIFV;
  160. imgFunc = imgMe;
  161. }
  162. if ("mouseover" === e.type || "" === e.type) {
  163. if (obj.href.match(/^https?:\/\/(.*\.)?gfycat\.com\/.{1,}$/i)) { // gfycat.com, HTML5 video even for direct .gif links
  164. clipFunc("//gfycat.com/cajax/get/" + obj.href.replace(/^.*\.com\//, ""), e);
  165. } else if (obj.href.match(/^https?:\/\/((i|m|www)\.)?imgur\.com\/[^\/]{1,}\.(gifv|mp4)$/)) { // imgur.com HTML5 video
  166. clipFunc(obj.href, e);
  167. } else if (obj.href.match(/https?:\/\/g\.redditmedia\.com\/[\w]+\.gif/)) { // g.redditmedia.com
  168. clipFunc(obj.href, e);
  169. } else if (obj.href.match(/\.(bmp|gif|jpe?g|png|svg)(\?.*)?$/i)) { // images, any domain
  170. imgFunc(obj.href, e);
  171. } else if (obj.href.match(/^https?:\/\/i\.reddituploads\.com\/.+/)) { // i.reddituploads.com
  172. imgFunc(obj.href, e);
  173. } else if (obj.href.match(/^https?:\/\/giphy\.com\/gifs\//)) { // this one giphy.com url pattern
  174. imgFunc(obj.href.replace(/\/gifs\/([\w]+-)*/, "/media/") + "/giphy.gif", e);
  175. } else if (obj.href.match(/^https?:\/\/((www|m)\.)?imgur\.com(\/r\/[^\/]*)?\/[^\/]{1,}#[0-9]{1,}$/)) { // imgur.com ad hoc collections
  176. var imgList = obj.href.slice(obj.href.lastIndexOf("/") + 1, obj.href.lastIndexOf("#")).split(",");
  177. var index = obj.href.slice(obj.href.lastIndexOf("#") + 1);
  178. imgFunc("//i.imgur.com/" + imgList[index] + ".jpg", e);
  179. } else if (obj.href.match(/^https?:\/\/((www|m)\.)?imgur\.com(\/r\/[^\/]*)?\/[^\/]{1,}$/)) { // imgur.com, nonhotlinked single images
  180. imgFunc(obj.href.replace(/\/([^\/]*\.)?imgur\.com(\/r\/[^\/]*)?/, "/i.imgur.com").replace(/\?.*/, "") + ".jpg", e);
  181. } else { // not a recognized image/clip link
  182. killMe();
  183. }
  184. } else { // mouseout
  185. killMe();
  186. }
  187. };
  188.  
  189. doc.head.appendChild(style);
  190. doc.body.appendChild(clip);
  191. doc.body.appendChild(img);
  192.  
  193. thumbs.forEach(function(thumb) {
  194. if (thumb.classList.contains("nsfw")) {
  195. return false;
  196. } else if (thumb.href.match(/\/r\/\w+\/comments\//) && thumb.querySelector('img')) {
  197. var expandoButton = thumb.nextSibling.querySelector(".expando-button");
  198. var expando = thumb.nextSibling.querySelector(".expando");
  199. return setTimeout(function() {
  200. expandoButton.click();
  201. expando.style.display = "none";
  202. thumb.href = expando.querySelector('img, video source').src;
  203. thumb.setAttribute("data-href-url", thumb.href);
  204. expandoButton.click();
  205. return swapper(thumb);
  206. });
  207. }
  208. swapper(thumb);
  209. });
  210.  
  211. for (i = 0; i < userText.length; i++) {
  212. var textLinks = userText[i].getElementsByClassName("md")[0].getElementsByTagName("a");
  213. if (textLinks.length) {
  214. for (j = 0; j < textLinks.length; j++) {
  215. swapper(textLinks[j]);
  216. }
  217. }
  218. }
  219. }());