Dropbox image viewer in Picarto

Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages. By StevenRoy

当前为 2024-11-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Dropbox image viewer in Picarto
  3. // @namespace http://michrev.com/
  4. // @description Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages. By StevenRoy
  5. // @include https://www.dropbox.com/*
  6. // @include https://picarto.tv/site/referrer*
  7. // @version 1.15
  8. // @grant none
  9. // @supportURL https://greasyfork.org/en/users/934871
  10. // ==/UserScript==
  11.  
  12. "use strict";
  13.  
  14. // _____________ __ __
  15. // / ___________/ / \,-' /
  16. // / /__ ___ / /\,-/ /
  17. // \___ \ / __\ / / / /
  18. //______/ / / / / / / / StevenRoy was here
  19. //_______/ /_/ /_/ /_/ 02023.08.23
  20.  
  21.  
  22.  
  23. // JB: issues/1092 obviously applies here.
  24.  
  25.  
  26.  
  27. // Sensitivity is proportional to the sum of the numbers being compared
  28. // When a is 200, returns true when b is >=197 and <=204
  29. // When a is 1000, returns true when b is >=981 and <=1020
  30. // (Can also be adjusted by changing the 100 constant but I like it where it is now.)
  31. function ratherclose(a,b) { return Math.abs(a-b)*100<(a+b); }
  32.  
  33. var img,mcx,mcy,ww,wh,dw,dh,dx,dy,isl,imgfit=0,eizs; // window and image size
  34. function resized() {
  35. if (!img) { return; } // TSNH - throw()? alert()?
  36. var de=document.documentElement;
  37. if (window.innerWidth) { // Preferred in FF because it doesn't shrink when a scrollbar is present.
  38. ww=window.innerWidth;
  39. wh=window.innerHeight;
  40. } else {
  41. if (de && de.clientWidth) { // Also exists in FF; this one excludes scrollbar if present.
  42. ww=de.clientWidth; // (Though that's kinda moot when we're using overflow:hidden)
  43. wh=de.clientHeight;
  44. } else {
  45. return false; // TSNH? Am I forgetting anything?
  46. }
  47. }
  48. // if (mcx===undefined && mcy===undefined) { mcx=ww>>1; mcy=wh>>1; } // until we get mouse coords
  49. var iw=img.w,ih=img.h; // We use these a lot.
  50. // var war=ww/wh; // window aspect ratio (1=square, >1=wide)
  51. var iar=iw/ih; // image aspect ratio (1920x800 -> 12/5 which is 2.4)
  52. var wscw=wh*iar; // resulting width from scaling window height to image's AR.
  53.  
  54. // image smaller than window: original size (centered), or enlarged to fit (letterboxed)
  55. // image larger than window: original size (pan), or shrunk to fit (letterboxed)
  56. // These are actually pretty much the same... except pan is only enabled in 1/4 cases
  57. // But there's a trickier case: Image smaller in one dimension but larger in the other.
  58. // I could give it two zoom states but neither is original-size ... or should I do the opposite?
  59.  
  60. // Let's just always assume three zoom-states: original (iw,ih), fit-x (ww,ww/iar), and fit-y (wh*iar,wh)
  61. // ...And then sort 'em. But first eliminate any that are nearly identical.
  62.  
  63. isl=[iw]; // image size list, start with original image size
  64. var am=ratherclose(ww,wscw); // aspect ratio match between image and window?
  65. if (!ratherclose(iw,ww)) { // but not actual size match.
  66. if (am) {
  67. console.log("Aspect ratio match");
  68. // isl.push(Math.floor(ww+wh*iar+1)>>1); // use average for near-match (This is actually bad.)
  69. // Assuming a very-near-but-not-perfect match: Which size is further from iw? Let's use that one.
  70. isl.push(Math.abs(iw-ww) > Math.abs(iw-wscw) ? ww : wscw);
  71. }
  72. else { isl.push(ww); }
  73. }
  74. if (!am && !ratherclose(iw,wscw)) { isl.push(wscw); }
  75. if (isl.length>1) {
  76. if (imgfit>=isl.length) { imgfit=isl.length-1; } // for those times when a size vanishes
  77. isl.sort((a,b) => a-b);
  78. img.style.cursor=(imgfit==isl.length-1)?"zoom-out":"zoom-in"; // How to affect blank space around img? (But do we want that?)
  79. } else { imgfit=0; img.style.cursor="default"; }
  80.  
  81. dw=isl[imgfit];
  82. dh=(dw==iw)?ih:(dw/iar); // dh=dw/(iw/ih) ... dh/ih=dw/iw
  83. if (eizs) { eizs.textContent=Math.round(100*dw/iw)+" %"; }
  84.  
  85. // panned(); // setting image size is done in here too now. Except it's now part of the animation process.
  86. }
  87.  
  88. function panned() { // Animated response to Mouse movement with requestAnimationFrame()
  89. if (mcx!==undefined && dw>ww) { dx=mcx*(ww-dw)/ww; } else { dx=(ww-dw)/2; }
  90. if (mcy!==undefined && dh>wh) { dy=mcy*(wh-dh)/wh; } else { dy=(wh-dh)/2; }
  91. var ics=img.style;
  92. ics.width=Math.round(dw)+"px"; // These were animated for awesomer zooming, but FF has problems with that!!!
  93. ics.height=Math.round(dh)+"px";
  94. ics.left=Math.round(dx)+"px"; // always negative when panning, otherwise positive and centered
  95. ics.top=Math.round(dy)+"px";
  96. }
  97.  
  98. function animate() {
  99. panned();
  100. // var c=coordstoimg();
  101. requestAnimationFrame(animate); // Uh oh, now we're doing it!
  102. }
  103.  
  104. // ** ** ** EVENTS
  105.  
  106. function shutup(e) {
  107. if (!e) { e=window.event; if (!e) { return false; } }
  108. e.cancelBubble = true;
  109. if (e.stopPropagation) e.stopPropagation(); // For when "return true" just isn't good enough?
  110. e.preventDefault(); // And I mean it!
  111. return e;
  112. }
  113.  
  114. function smother(evt) { shutup(evt); return false; }
  115.  
  116. function mousecoords(e) { // event
  117. if ("pageX" in e && "pageY" in e) {
  118. mcx = e.pageX; mcy = e.pageY;
  119. } else { // We want coords relative to top (origin) of document, this should do it:
  120. mcx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  121. mcy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  122. }
  123. // x -= c.offsetLeft;
  124. // y -= c.offsetTop;
  125. // return [x,y];
  126. }
  127.  
  128. function smdown(evt) {
  129. // var e=shutup(evt);
  130. imgfit++;
  131. if (imgfit>=isl.length) { imgfit=0; } // Select next (or first) size
  132. resized();
  133. smmove(evt); // trigger move() after coordinates change
  134. return false; // Does this value do anything?
  135. }
  136.  
  137. function smmove(evt) {
  138. var e=shutup(evt);
  139. mousecoords(e);
  140. // panned();
  141. return false;
  142. }
  143.  
  144. function makezoomableimage(i) {
  145. img=i;
  146. img.w=img.naturalWidth;
  147. img.h=img.naturalHeight;
  148.  
  149. window.onresize=resized; resized();
  150. // i.onmousedown=smdown;
  151. i.onmousedown=smother;
  152. i.onmouseup=smother; // not used!
  153. window.onmousemove=smmove;
  154. // i.onclick=smother; // none of these used either
  155. i.onclick=smdown; // alternative strategy, maybe less good, maybe not?
  156. i.ondblclick=smother;
  157. // i.oncontextmenu=smother;
  158. animate(); // This starts the maaaaagic!
  159. img.style.position="fixed";
  160. img.style.maxWidth="unset"; // Leave it to Picarto to screw with zooms...
  161. return i;
  162. }
  163.  
  164. const c64pal="000 fff c05 6ef c5d 6c5 42b fe7 c84 641 f79 555 888 cfa a9f ccc";
  165. // That's my c64 palette. I'm going for a balance of authentic and vivid.
  166. var waitindicator=function(){
  167. var waittimer,wc=4,wde,wrefc=0;
  168. return function(){
  169. if(!wde) {
  170. document.body.appendChild(wde=Object.assign(document.createElement("div"),{
  171. innerHTML:'W<span>a</span><span>i</span><span>t</span><span>.</span><span>.</span><span>.</span>',
  172. // id:'loading',
  173. style:"z-index:90999;box-sizing:border-box;font-size:20pt;line-height:18pt;font-family:" /* 'Roboto', */ +"'Andale Mono',monospace;"+
  174. "position:fixed;height:30px;width:160px;top:50%;left:50%;text-align:center;"+
  175. "margin:-15px 0 0 -80px;background:#000;color:#fff"
  176. }));
  177. }
  178. if (wde && !waittimer) { waittimer=setInterval(function(){
  179. if (!wde) { return clearInterval(waittimer); } // never underestimate the capacity of browsers to screw up
  180. wc=(wc+4)&60;
  181. var wcc=wc;
  182. wde.style.color="#"+c64pal.substr(wc,3); // A very specific set of colors...
  183. Array.prototype.forEach.call(wde.children,(e)=>{
  184. wcc=wcc+4&60;
  185. e.style.color="#"+c64pal.substr(wcc,3);
  186. });
  187. },150); }
  188. wrefc++;
  189. return function(){
  190. if (wrefc>0) { wrefc--; }
  191. setTimeout(()=>{
  192. if (wrefc==0 && wde) { wde.parentNode.removeChild(wde); wde=false; }
  193. },250); // in case of sequential busy-states, delay vanish of busy indicator
  194. };
  195. };
  196. }();
  197.  
  198. // So much gutting of previous code because we don't
  199. // need -any- of the canvas functions...
  200. function loadimage(src,cb) {
  201. var wi=waitindicator();
  202. var ix=document.createElement("img");
  203. // ix=new Image();
  204. ix.onload=function() {
  205. wi(); cb(ix); // pass img to callback
  206. }
  207. ix.src=src;
  208. } // that basically replaces an entire image manipulation library!
  209. // (Although replacing <canvas> with <image> does seem to make this perform worse (and often crash) in FF52
  210. // if my awesome animation function is used... I just won't use it then. Problem solved.)
  211.  
  212. // Freaking dummkopfs Picarto seems to like to lazy-load all its actual page content,
  213. // meaning things we want to replace won't exist at this function's execute-time.
  214. // Of course there's a stupid workaround...
  215. function itsapicartopage(addr){
  216. // addr will look like: "https:// www.dropbox.com/s/y88o006k1ol8ryj/alkalithemorningafter.png?dl=0"
  217. var m=parsedbaddr(addr);
  218. if (m) {
  219. var keeplink=document.links;
  220. if (keeplink && keeplink[0] && keeplink[0].href==addr) {
  221. keeplink=keeplink[0].parentNode;
  222.  
  223. loadimage(m,(ix)=>{
  224. var mb=document.getElementById("main-container"); // within div#root within body
  225. if (mb && mb.children) mb=mb.childNodes[0];
  226. console.log(mb,keeplink);
  227. if (mb && keeplink) {
  228. mb.innerHTML='';
  229. mb.style="position:fixed; overflow:hidden; min-height:100vh; height:100vh; width:100%; padding:0; margin:0; display:block";
  230. keeplink.style="position:fixed;right:0;bottom:0;margin:20px";
  231. eizs=document.createElement("span");
  232. mb.appendChild(makezoomableimage(ix));
  233. mb.appendChild(keeplink);
  234. var d1=document.createElement("div"); // additional UI element, mainly for balanced aesthetics!
  235. d1.className=keeplink.className; // This may vary!
  236. d1.style="position:fixed;left:0;bottom:0;margin:20px;text-align:center";
  237. keeplink.style.backgroundColor=d1.style.backgroundColor=getComputedStyle(document.body).backgroundColor;
  238. d1.innerHTML=img.w+" x "+img.h+"<BR />";
  239. mb.appendChild(d1);
  240. d1.appendChild(eizs);
  241. } else console.log("Fail");
  242. });
  243. } else { console.log("Retrying"); setTimeout(itsapicartopage,200,addr); }
  244. }
  245. }
  246.  
  247. function parsedbaddr(a){
  248. // console.log("Parsing addr:",a);
  249. var m=a.match(/\/\/(?:www\.)?dropbox\.com\/(?:e\/)?scl\/fi\/([^/?.]+)\/.+\?(.+)$/i); // where does the e go?
  250. if (m && m.length && m[2]) {
  251. // console.log("2:",m);
  252. var rlkey=false;
  253. m[2].split('&').forEach(n=>{
  254. if (rlkey) return; // found one so stop looking
  255. var a=n.split('=');
  256. if ((a.length==2) && (a[0]=='rlkey')) rlkey=a[1];
  257. });
  258. // TODO: Is this size= parameter needed? Find out what size_mode does.
  259. if (rlkey) return "https://www.dropbox.com/temp_thumb_from_token/c/"+m[1]+"?rlkey="+rlkey+"&size=1200x1200&size_mode=4";
  260. return false;
  261. }
  262. m=a.match(/\/\/(?:www\.)?dropbox\.com\/.+\/[^/?.]*\.(jpg|jpeg|png|gif|webp)(\?dl=0)?$/);
  263. if (m && m.length && m[1]) { // It's a valid address...
  264. // console.log("1:",m);
  265. if (m[2] == '?dl=0') a=a.replace(/\?dl=0/,'');
  266. else if (m[2]) throw ("TSNH: Weird query in db addr");
  267. return a+'?dl=1'; // Functioning link to the image itself
  268. }
  269. return false;
  270. }
  271.  
  272. var l=top.location.href;
  273. var m=l.match(/\/\/(?:www\.)?picarto\.tv\/.+?go=(http(?:s)?%3A%2F%2F[^&]+)/);
  274. if (m && m.length && m[1]) itsapicartopage(decodeURIComponent(m[1]));
  275.  
  276. // (Also, the Dropbox site is broken in Firefox 52 but we can detect that
  277. // and force images to appear there anyway. This one's for the WinXP users. Shrug.)
  278. else if (m=parsedbaddr(l)) { // If we're on the dropbox site...
  279. var bv=navigator.userAgent.match(/Firefox\/([0-9]+)/); // but using a browser that db WON'T WORK ON
  280. if (bv && bv[1] && (bv[1]-0)<=52) {
  281. var i0=document.createElement("img"); // Just dump the image (sorry, no fancy viewer this time)
  282. i0.src=m;
  283. document.body.appendChild(i0);
  284. }
  285. }
  286.