Dropbox image viewer in Picarto

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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Dropbox image viewer in Picarto
// @namespace   http://michrev.com/
// @description Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages. By StevenRoy
// @include     https://www.dropbox.com/*
// @include     https://picarto.tv/site/referrer*
// @include     https://www.picarto.tv/site/referrer*
// @version     1.16
// @grant       none
// @supportURL  https://greasyfork.org/en/users/934871
// ==/UserScript==

"use strict";

//     _____________  __    __
//    / ___________/ /  \,-' /
//   / /__    ___   / /\,-/ /
//   \___ \  / __\ / /   / /
//______/ / / /   / /   / /    StevenRoy was here
//_______/ /_/   /_/   /_/     02023.08.23



// JB: issues/1092 obviously applies here.



// Sensitivity is proportional to the sum of the numbers being compared
// When a is 200, returns true when b is >=197 and <=204
// When a is 1000, returns true when b is >=981 and <=1020
// (Can also be adjusted by changing the 100 constant but I like it where it is now.)
function ratherclose(a,b) { return Math.abs(a-b)*100<(a+b); }

var img,mcx,mcy,ww,wh,dw,dh,dx,dy,isl,imgfit=0,eizs; // window and image size
function resized() {
  if (!img) { return; } // TSNH - throw()? alert()?
  var de=document.documentElement;
  if (window.innerWidth) { // Preferred in FF because it doesn't shrink when a scrollbar is present.
    ww=window.innerWidth;
    wh=window.innerHeight;
  } else {
    if (de && de.clientWidth) { // Also exists in FF; this one excludes scrollbar if present.
      ww=de.clientWidth;  // (Though that's kinda moot when we're using overflow:hidden)
      wh=de.clientHeight;
    } else {
      return false; // TSNH? Am I forgetting anything?
    }
  }
//  if (mcx===undefined && mcy===undefined) { mcx=ww>>1; mcy=wh>>1; } // until we get mouse coords
  var iw=img.w,ih=img.h; // We use these a lot.
//  var war=ww/wh; // window aspect ratio (1=square, >1=wide)
  var iar=iw/ih; // image aspect ratio (1920x800 -> 12/5 which is 2.4)
  var wscw=wh*iar; // resulting width from scaling window height to image's AR.

// image smaller than window: original size (centered), or enlarged to fit (letterboxed)
// image larger than window: original size (pan), or shrunk to fit (letterboxed)
// These are actually pretty much the same... except pan is only enabled in 1/4 cases
// But there's a trickier case: Image smaller in one dimension but larger in the other.
// I could give it two zoom states but neither is original-size ... or should I do the opposite?

// Let's just always assume three zoom-states: original (iw,ih), fit-x (ww,ww/iar), and fit-y (wh*iar,wh)
// ...And then sort 'em. But first eliminate any that are nearly identical.

  isl=[iw]; // image size list, start with original image size
  var am=ratherclose(ww,wscw); // aspect ratio match between image and window?
  if (!ratherclose(iw,ww)) { // but not actual size match.
    if (am) {
      console.log("Aspect ratio match");
//      isl.push(Math.floor(ww+wh*iar+1)>>1); // use average for near-match (This is actually bad.)
// Assuming a very-near-but-not-perfect match: Which size is further from iw? Let's use that one.
      isl.push(Math.abs(iw-ww) > Math.abs(iw-wscw) ? ww : wscw);
    }
    else { isl.push(ww); }
  }
  if (!am && !ratherclose(iw,wscw)) { isl.push(wscw); }
  if (isl.length>1) {
    if (imgfit>=isl.length) { imgfit=isl.length-1; } // for those times when a size vanishes
    isl.sort((a,b) => a-b);
    img.style.cursor=(imgfit==isl.length-1)?"zoom-out":"zoom-in"; // How to affect blank space around img? (But do we want that?)
  } else { imgfit=0; img.style.cursor="default"; }

  dw=isl[imgfit];
  dh=(dw==iw)?ih:(dw/iar); // dh=dw/(iw/ih) ... dh/ih=dw/iw
  if (eizs) { eizs.textContent=Math.round(100*dw/iw)+" %"; }

//  panned(); // setting image size is done in here too now. Except it's now part of the animation process.
}

function panned() { // Animated response to Mouse movement with requestAnimationFrame()
  if (mcx!==undefined && dw>ww) { dx=mcx*(ww-dw)/ww; } else { dx=(ww-dw)/2; }
  if (mcy!==undefined && dh>wh) { dy=mcy*(wh-dh)/wh; } else { dy=(wh-dh)/2; }
  var ics=img.style;
  ics.width=Math.round(dw)+"px"; // These were animated for awesomer zooming, but FF has problems with that!!!
  ics.height=Math.round(dh)+"px";
  ics.left=Math.round(dx)+"px"; // always negative when panning, otherwise positive and centered
  ics.top=Math.round(dy)+"px";
}

function animate() {
  panned();
//  var c=coordstoimg();
  requestAnimationFrame(animate); // Uh oh, now we're doing it!
}

// ** ** ** EVENTS

function shutup(e) {
  if (!e) { e=window.event; if (!e) { return false; } }
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation(); // For when "return true" just isn't good enough?
  e.preventDefault(); // And I mean it!
  return e;
}

function smother(evt) { shutup(evt); return false; }

function mousecoords(e) { // event
  if ("pageX" in e && "pageY" in e) {
    mcx = e.pageX; mcy = e.pageY;
  } else { // We want coords relative to top (origin) of document, this should do it:
    mcx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    mcy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
//  x -= c.offsetLeft;
//  y -= c.offsetTop;
//  return [x,y];
}

function smdown(evt) {
//  var e=shutup(evt);
  imgfit++;
  if (imgfit>=isl.length) { imgfit=0; } // Select next (or first) size
  resized();
  smmove(evt); // trigger move() after coordinates change
  return false; // Does this value do anything?
}

function smmove(evt) {
  var e=shutup(evt);
  mousecoords(e);
//  panned();
  return false;
}

function makezoomableimage(i) {
  img=i;
  img.w=img.naturalWidth;
  img.h=img.naturalHeight;

  window.onresize=resized; resized();
//  i.onmousedown=smdown;
  i.onmousedown=smother;
  i.onmouseup=smother; // not used!
  window.onmousemove=smmove;
//  i.onclick=smother; // none of these used either
  i.onclick=smdown; // alternative strategy, maybe less good, maybe not?
  i.ondblclick=smother;
//  i.oncontextmenu=smother;
  animate(); // This starts the maaaaagic!
  img.style.position="fixed";
  img.style.maxWidth="unset"; // Leave it to Picarto to screw with zooms...
  return i;
}

const c64pal="000 fff c05 6ef c5d 6c5 42b fe7 c84 641 f79 555 888 cfa a9f ccc";
// That's my c64 palette. I'm going for a balance of authentic and vivid.
var waitindicator=function(){
	var waittimer,wc=4,wde,wrefc=0;
	return function(){
		if(!wde) {
			document.body.appendChild(wde=Object.assign(document.createElement("div"),{
				innerHTML:'W<span>a</span><span>i</span><span>t</span><span>.</span><span>.</span><span>.</span>',
//				id:'loading',
				style:"z-index:90999;box-sizing:border-box;font-size:20pt;line-height:18pt;font-family:" /* 'Roboto', */ +"'Andale Mono',monospace;"+
					"position:fixed;height:30px;width:160px;top:50%;left:50%;text-align:center;"+
					"margin:-15px 0 0 -80px;background:#000;color:#fff"
			}));
		}
		if (wde && !waittimer) { waittimer=setInterval(function(){
			if (!wde) { return clearInterval(waittimer); } // never underestimate the capacity of browsers to screw up
			wc=(wc+4)&60;
			var wcc=wc;
			wde.style.color="#"+c64pal.substr(wc,3); // A very specific set of colors...
			Array.prototype.forEach.call(wde.children,(e)=>{
				wcc=wcc+4&60;
				e.style.color="#"+c64pal.substr(wcc,3);
			});
		},150); }
		wrefc++;
		return function(){
			if (wrefc>0) { wrefc--; }
			setTimeout(()=>{
				if (wrefc==0 && wde) { wde.parentNode.removeChild(wde); wde=false; }
			},250); // in case of sequential busy-states, delay vanish of busy indicator
		};
	};
}();

// So much gutting of previous code because we don't
// need -any- of the canvas functions...
function loadimage(src,cb) {
	var wi=waitindicator();
	var ix=document.createElement("img");
//  ix=new Image();
	ix.onload=function() {
		wi(); cb(ix); // pass img to callback
	}
	ix.src=src;
} // that basically replaces an entire image manipulation library!
// (Although replacing <canvas> with <image> does seem to make this perform worse (and often crash) in FF52
//   if my awesome animation function is used... I just won't use it then. Problem solved.)

// Freaking dummkopfs Picarto seems to like to lazy-load all its actual page content,
// meaning things we want to replace won't exist at this function's execute-time.
// Of course there's a stupid workaround...
function itsapicartopage(addr){
// addr will look like: "https:// www.dropbox.com/s/y88o006k1ol8ryj/alkalithemorningafter.png?dl=0"
	var m=parsedbaddr(addr);
	if (m) {
		var keeplink=document.links;
		if (keeplink && keeplink[0] && keeplink[0].href==addr) {
			keeplink=keeplink[0].parentNode;

			loadimage(m,(ix)=>{
				var mb=document.getElementById("main-container"); // within div#root within body
				if (mb && mb.children) mb=mb.childNodes[0];
				console.log(mb,keeplink);
				if (mb && keeplink) {
					mb.innerHTML='';
					mb.style="position:fixed; overflow:hidden; min-height:100vh; height:100vh; width:100%; padding:0; margin:0; display:block";
					keeplink.style="position:fixed;right:0;bottom:0;margin:20px";
					eizs=document.createElement("span");
					mb.appendChild(makezoomableimage(ix));
					mb.appendChild(keeplink);
					var d1=document.createElement("div"); // additional UI element, mainly for balanced aesthetics!
					d1.className=keeplink.className; // This may vary!
					d1.style="position:fixed;left:0;bottom:0;margin:20px;text-align:center";
					keeplink.style.backgroundColor=d1.style.backgroundColor=getComputedStyle(document.body).backgroundColor;
					d1.innerHTML=img.w+" x "+img.h+"<BR />";
					mb.appendChild(d1);
					d1.appendChild(eizs);
				} else console.log("Fail");
			});
		} else { console.log("Retrying"); setTimeout(itsapicartopage,200,addr); }
	}
}

function parsedbaddr(a){
//	console.log("Parsing addr:",a);
	var m=a.match(/\/\/(?:www\.)?dropbox\.com\/(?:e\/)?scl\/fi\/([^/?.]+)\/.+\?(.+)$/i); // where does the e go?
	if (m && m.length && m[2]) {
//		console.log("2:",m);
		var rlkey=false;
		m[2].split('&').forEach(n=>{
			if (rlkey) return; // found one so stop looking
			var a=n.split('=');
			if ((a.length==2) && (a[0]=='rlkey')) rlkey=a[1];
		});
// TODO: Is this size= parameter needed? Find out what size_mode does.
		if (rlkey) return "https://www.dropbox.com/temp_thumb_from_token/c/"+m[1]+"?rlkey="+rlkey+"&size=1200x1200&size_mode=4";
		return false;
	}
	m=a.match(/\/\/(?:www\.)?dropbox\.com\/.+\/[^/?.]*\.(jpg|jpeg|png|gif|webp)(\?dl=0)?$/);
	if (m && m.length && m[1]) { // It's a valid address...
//		console.log("1:",m);
		if (m[2] == '?dl=0') a=a.replace(/\?dl=0/,'');
		else if (m[2]) throw ("TSNH: Weird query in db addr");
		return a+'?dl=1'; // Functioning link to the image itself
	}
	return false;
}

var l=top.location.href;
var m=l.match(/\/\/(?:www\.)?picarto\.tv\/.+?go=(http(?:s)?%3A%2F%2F[^&]+)/);
if (m && m.length && m[1]) itsapicartopage(decodeURIComponent(m[1]));

// (Also, the Dropbox site is broken in Firefox 52 but we can detect that
// and force images to appear there anyway. This one's for the WinXP users. Shrug.)
else if (m=parsedbaddr(l)) { // If we're on the dropbox site...
	var bv=navigator.userAgent.match(/Firefox\/([0-9]+)/); // but using a browser that db WON'T WORK ON
	if (bv && bv[1] && (bv[1]-0)<=52) {
		var i0=document.createElement("img"); // Just dump the image (sorry, no fancy viewer this time)
		i0.src=m;
		document.body.appendChild(i0);
	}
}