Greasy Fork

来自缓存

Greasy Fork is available in English.

Plex now playing badge

Display a badge on favicon with a number of users streaming from the server

当前为 2017-09-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Plex now playing badge
// @namespace   V@no
// @description Display a badge on favicon with a number of users streaming from the server
// @include     http://localhost:32400/web/*
// @include     http://127.0.0.1:32400/web/*
// @include     https://app.plex.tv/desktop
// @include     https://app.plex.tv/desktop/*
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACVklEQVRYhc2Xy08TURTGZ6NAn7RMp1xo587ChVUTY9mCCfFPMFZicGlMNNGli7YoIsYBNDyCDyKmQVGpQMJC0aVuTKCkoFCbEhQoCNrUkBDWn4uWR8s0tHVu603O+vzuPed857scpRTFDO6/Bxi4VIWAhyjGpJvglENkC1B3UsSEWxkg4CHwX6liC0Apxc2ztowAU02VcNXZ2QIcPUIxmfYKU16CYLMVM3cEfG6yQpIYN2Grqzrl1tO3rfh6V8CcbMG3Dh5yI2ELIEkUn24QBLwE082J5KE2HuEHFYh0mRHpNMN5LPeGzGkMz9XaEbxViS+tAuaSyee7zfj+sByLT4x457awBaCUYvgawew9C8L3d5Mv9RkRfWbAqk+PC2eq2QI4HSJmZR6RTjMWek1YTCb/OaDD+qAW4UeGnBoyLyWUGwnme0z48bgc0f5E8l8vtYj5NYiPlKHrssAWQJIogjKPpT4jVnx6rA9qERvS4M9oKTbGShAfLUXNiey0Ie9d0FBvw/LT5O1faREfLsPGWAk23x7G1vtD+NhhYAtAKcW4h8facx1+v9bgA8flFf8EUHNcRNSnR2yo2AD+XYBsjyoA414eay90KS9QMICGehui/YZ9PbB9OI5TDFUAJIki2FaRMgUFBZAvEiz0mlJ04KASqAbgdIgItfM7SricVMKCAYxcJwi1798FmQBULYGr1o6ZFqviNmQOsG1IMvkB5iXYsWQZHBFTgHRTmu4JJ1oEtmOoZMv3uuLzp23sAA76mLy5mnDDzKQ4269ZUZbR3ijKOlYD4C/uwVlNS+Cv+wAAAABJRU5ErkJggg==
// @require     https://openuserjs.org/src/libs/sizzle/GM_config.js
// @author      V@no
// @version     2.2
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_log
// @grant       GM_registerMenuCommand
// ==/UserScript==

var prefsDefault = {
	position: 2, //0 = top-left, 1 = top-right, 2 = bottom-right, 3 = bottom-left
	offsetX: 0, //move badge away from x egde (use negative numbers for auto scale based on text size)
	offsetY: 0, //move badge away from y edge (use negative numbers for auto scale based on text size)
	textSize: 0, //text size, 0 = auto, 1 = 5px, 2 = 10px, so on
	textMargin: -1, //margin around text, use negative number for auto scale based on text size
	textColor: "#000000", // text color
	backgroundColor: "#FFFFFF", //background color
	borderColor: "#B90000", //border color
	borderWidth: -1, //border width, -1 = auto based on text size
	borderRadius: 0, //border corners radius
	sizeIcon: 16, //image size in pixels, 0 = original
};


var prefsConfig = {
	position: ["Top-Left", "Top-Right", "Bottom-Right", "Bottom-Left"],
	positionReal: ["0", "1", "2", "3"],
	sizeIcon: ["Original", "16x16", "32x32"],
	sizeIconReal: ["0", "16", "32"],
	textSize: ["Auto", "1", "2", "3"],
	textSizeReal: ["0", "1", "2", "3"],
	borderWidth: ["Auto", "0", "1", "2", "3", "4", "5", "6", "7", "8"],
	borderWidthReal: ["-1", "0", "1", "2", "3", "4", "5", "6", "7", "8"],
	textMargin: ["Auto x4", "Auto x3", "Auto x2", "Auto", " 0", " 1", " 2", " 3", " 4"],
	textMarginReal: ["-4", "-3", "-2", "-1", " 0", " 1", " 2", " 3", " 4"],
	get: function(id, val, real)
	{
		let pos = this[id].indexOf(val);
		if (pos == -1)
		{
			if (this[id+"Real"])
				pos = this[id+"Real"].indexOf(String(val));

			if (pos == -1)
			{
				pos = prefs[id];
			}
		}
		if (real && this[id+"Real"])
		{
			pos = this[id+"Real"][pos];
			pos = fixType.do(id, pos);
		}
		else
			pos = this[id][pos];

		return pos;
	}
};

function clone(orig)
{
	let obj = {};
	for (let n in orig)
		obj[n] = orig[n];

	return obj;
}

function prefsUpdate (temp)
{
	for(let n in prefs)
	{
		let pref = prefs[n];
		if (typeof(pref) == "object" || typeof(pref) == "function")
			continue;

		pref = temp && GM_config.isOpen ? GM_config.fields[n].toValue() : GM_config.get(n);
		if (n in prefsConfig)
			pref = prefsConfig.get(n, pref, true);

		pref = fixType.do(prefs[n], pref);

		if (n.indexOf("Color") != -1)
			pref = pref.replace(/[^#a-zA-Z0-9\-]+/g, "");

		prefs[n] = pref;
	}
}
var prefs = clone(prefsDefault),
		prefsClone = {},
		fixType = {
			string: String,
			number: parseInt,
			boolean: Boolean,
			do: function(o, n)
			{
				if (typeof(o) in this)
					n = this[typeof(o)](n);

				return n;
			}
		},
		link = null,
		head = document.getElementsByTagName('head')[0],
		prev = null,
		img = new Image(),
		canvas = document.createElement('canvas'),
		ctx = canvas.getContext('2d'),
		size = 16,
		multi,
		nav,
		configBlur = document.createElement("div"),
		title = "Plex Badge Configuration",
		style = document.createElement("style"),
		css = function(){/*
#configBlur
{
	z-index: 9998;
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	background-color: grey;
	opacity: 0.6;
	display: none;
}
body[config] #configBlur
{
	display: block;
}
body[config] #plex > div
{
	filter: blur(5px);
}
*/};

style.innerHTML = css.toString().slice(14,-3).split("*//*").join("*/");
head.appendChild(style);
configBlur.id = "configBlur";
document.body.appendChild(configBlur);
configBlur.addEventListener("click", function(){GM_config.close();}, false);

/*
if (typeof GM_getMetadata === 'function') // Scriptish
	title = GM_getMetadata('name');
else if (typeof GM_info !== 'undefined') // Greasemonkey, Tampermonkey &c.
	title = GM_info.script.name;

title += (title ? ' ' : '') + 'Configuration';
*/
GM_config.init({
	id: "plexNowPlayingBadge",
	title: title,
	fields: // Fields object
	{
		position: {
				type: "select",
				label: "Position",
				labelPos: "left",
				options: prefsConfig.position,
				default: prefsConfig.position[prefsDefault.position]
		},
		offsetX: {
				type: "select",
				label: "Offset X",
				labelPos: "left",
				options: Array.apply(null, {length: 17}).map(function(v,i){return String(i);}), //0-16
				default: prefs.offsetX
		},
		offsetY: {
				type: "select",
				label: "Offset Y",
				labelPos: "left",
				options: Array.apply(null, {length: 17}).map(function(v,i){return String(i);}), //0-16
				default: prefs.offsetY
		},
		textSize: {
				type: "select",
				label: "Text size",
				labelPos: "left",
				options: prefsConfig.textSize,
				default: prefsConfig.textSize[prefsDefault.textSize]
		},
		textMargin: {
				type: "select",
				label: "Text margin",
				labelPos: "left",
				options: prefsConfig.textMargin,
				default: prefsConfig.get("textMargin", prefsDefault.textMargin)
		},
		textColor: {
				type: "text",
				label: "Text color",
				labelPos: "left",
				default: prefs.textColor
		},
		backgroundColor: {
				type: "text",
				label: "Background color",
				labelPos: "left",
				default: prefs.backgroundColor
		},
		borderColor: {
				type: "text",
				label: "Border color",
				labelPos: "left",
				default: prefs.borderColor
		},
		borderWidth: {
				type: "select",
				labelPos: "left",
				label: "Border width",
				options: prefsConfig.borderWidth,
				default: prefsConfig.get("borderWidth", prefsDefault.borderWidth)
		},
		borderRadius: {
				type: "select",
				label: "Border radius",
				labelPos: "left",
				options: ["0", "1", "2", "3", "4", "5"],
				default: prefs.borderRadius
		},
		sizeIcon: {
				type: "select",
				label: "Icon size",
				labelPos: "left",
				options: prefsConfig.sizeIcon,
				default: prefsConfig.get("sizeIcon", prefsDefault.sizeIcon)
		}
	},
	events: {
		open: function()
		{
			prefsClone = clone(prefs);
			GM_config.frame.style.width = "24em";
			GM_config.frame.style.height = GM_config.frame.contentDocument.defaultView.document.body.firstChild.clientHeight + 20 + "px";
			GM_config.frame.style.border = "0";
			GM_config.frame.style.boxShadow = "0px 0px 100px 10px #000";
			GM_config.center();
			document.body.setAttribute("config","");
			let input = function(e)
			{
				prefsUpdate(true);
				loop(true);
			};
			for(let f in GM_config.fields)
			{
				if (!(f in prefs))
					continue;

				let field = GM_config.fields[f];
				field.node.addEventListener("input", input, true);
			}
			loop(null);
		},
		close: function()
		{
			document.body.removeAttribute("config");
			prefs = clone(prefsClone);
			prefsUpdate();
			loop(false);
		},
		save: function()
		{
			prefsClone = clone(prefs);
			prefsUpdate();
			loop(true);
		},
		reset: function()
		{
			prefs = clone(prefsDefault);
			prefsUpdate(true);
			loop(true);
		}
	},
	css: [
		".config_header{margin-bottom:1em !important;}"+
		".config_var{display:table-row;}"+
		".config_var > label{display:table-cell;white-space:nowrap;text-align:end;width:0;}"+
		".config_var > *{margin:3px 3px 3px 0.5em;width:90%}"
	]
});

GM_registerMenuCommand(title, function () { GM_config.open(); });

function drawText(text)
{
	if (!img.loaded)
		return;

	if (prefs.sizeIcon)
		img.width = img.height = prefs.sizeIcon;
	else
	{
		img.width = img._width;
		img.height = img._height;
	}
	let size = img.height;
	canvas.width = canvas.height = size;
	ctx.save();
	ctx.drawImage(img, 0, 0, size, size);

	if (text)
	{
		let multi = prefs.textSize ? prefs.textSize : size / 16,
				textArray = text.toString().toUpperCase().split(''),
				textHeight = 0,
				textWidth = textArray.reduce(function (prev, cur)
				{
					let px = getPixelMap(cur);
					if (px.length * multi > textHeight)
						textHeight = px.length * multi;

					return prev + px[0].length * multi + 1;
				}, -1),
				borderWidth = prefs.borderWidth == -1 ? multi : prefs.borderWidth,
				textMargin = prefs.textMargin < 0 ? -prefs.textMargin * multi : prefs.textMargin,
				width = textWidth + textMargin * 2 + borderWidth,
				height = textHeight + textMargin * 2 + borderWidth,
				xy = -borderWidth / 2,
				offsetX = prefs.offsetX < 0 ? -prefs.offsetX * multi : prefs.offsetX,
				offsetY = prefs.offsetY < 0 ? -prefs.offsetY * multi : prefs.offsetY,
				x, y;

		switch (prefs.position)
		{
			case 0:
				x = borderWidth + offsetX;
				y = borderWidth + offsetY;
				break;
			case 1:
				x = size - width - offsetX;
				y = borderWidth + offsetY;
				break;
			default:
			case 2:
				x = size - width - offsetX;
				y = size - height - offsetY;
				break;
			case 3:
				x = borderWidth + offsetX;
				y = size - height - offsetX;
				break;
		}
		ctx.translate(x, y);

		// Draw Box
		ctx.fillStyle = prefs.backgroundColor;//backgborderRadius
		ctx.strokeStyle = prefs.borderColor;//border

		ctx.lineWidth = borderWidth;
		ctx.borderRadiusRect(xy, xy, width, height, prefs.borderRadius * multi).fill();
		if (borderWidth)
			ctx.borderRadiusRect(xy, xy, width, height, prefs.borderRadius * multi).stroke();

		// Draw Text
		ctx.fillStyle = prefs.textColor;
		ctx.translate(textMargin, textMargin);
		for(let i = 0; i < textArray.length; i++)
		{
			let px = getPixelMap(textArray[i]),
					_y = 0;

			for (let y = 0; y < px.length; y++)
			{
				let _x = 0;
				for (let x = 0; x < px[y].length; x++)
				{
					if (px[y] && px[y][x])
					{
						for(let mx = 0; mx < multi; mx++)
						{
							for(let my = 0; my < multi; my++)
							{
								ctx.fillRect(_x + mx, _y + my, 1, 1);
							}
						}
					}
					_x += multi;
				}
				_y += multi;
			}
			ctx.translate(px[0].length * multi + 1, 0);
		}
	}
	let data = canvas.toDataURL("image/x-icon");
	ctx.restore();
	ctx.clearRect(0, 0, size, size);
	return data;
}//drawText()

//borrowed from https://chrome.google.com/webstore/detail/favicon-badges/fjnaohmeicdkcipkhddeaibfhmbobbfm/related?hl=en-US
/**
 * Gets a character's pixel map
 */
function getPixelMap(sym)
{
	let px = PIXELMAPS[sym];
	if (!px)
		px = PIXELMAPS['0'];
	return px;
}
var PIXELMAPS = {
	'0': [
		[1,1,1],
		[1,0,1],
		[1,0,1],
		[1,0,1],
		[1,1,1]
	],
	'1': [
		[0,1,0],
		[1,1,0],
		[0,1,0],
		[0,1,0],
		[1,1,1]
	],
	'2': [
		[1,1,1],
		[0,0,1],
		[1,1,1],
		[1,0,0],
		[1,1,1]
	],
	'3': [
		[1,1,1],
		[0,0,1],
		[0,1,1],
		[0,0,1],
		[1,1,1]
	],
	'4': [
		[1,0,1],
		[1,0,1],
		[1,0,1],
		[1,1,1],
		[0,0,1]
	],
	'5': [
		[1,1,1],
		[1,0,0],
		[1,1,1],
		[0,0,1],
		[1,1,1]
	],
	'6': [
		[1,1,1],
		[1,0,0],
		[1,1,1],
		[1,0,1],
		[1,1,1]
	],
	'7': [
		[1,1,1],
		[0,0,1],
		[0,0,1],
		[0,1,0],
		[0,1,0]
	],
	'8': [
		[1,1,1],
		[1,0,1],
		[1,1,1],
		[1,0,1],
		[1,1,1]
	],
	'9': [
		[1,1,1],
		[1,0,1],
		[1,1,1],
		[0,0,1],
		[1,1,1]
	],
};

//https://stackoverflow.com/a/7838871/2930038
CanvasRenderingContext2D.prototype.borderRadiusRect = function (x, y, w, h, r)
{
	if (w < 2 * r) r = w / 2;
	if (h < 2 * r) r = h / 2;
	this.beginPath();
	this.moveTo(x+r, y);
	this.arcTo(x+w,  y,   x+w, y+h, r);
	this.arcTo(x+w,  y+h, x,   y+h, r);
	this.arcTo(x,    y+h, x,   y,   r);
	this.arcTo(x,    y,   x+w, y,   r);
	this.closePath();
	return this;
};

function loop(conf)
{
	let isConf = typeof(conf) != "undefined";
	if (!isConf)
		clearTimeout(loop.timer);

	if (!link || link.parentNode !== head)
	{
		let links = document.getElementsByTagName("link");
		for(let i = 0; i < links.length; i++)
		{
			if (links[i].getAttribute("rel") == "shortcut icon")
			{
				link = links[i];
				break;
			}
		}
		if (link && !img.loaded)
		{
			img.setAttribute('crossOrigin','anonymous');
			img.src = link.href;
			img.onload = function()
			{
				img._width = img.width;
				img._height = img.height;
				img.loaded = true;
			};
		}
	}
	let data,
			_badge = document.getElementsByClassName("activity-badge badge badge-transparent"),
			badge = _badge.length ? _badge[0].innerText : "",
			text = parseInt(badge);

	if(conf === true)
	{
		text = prev;
		prev = null;
	}
	else if ((isConf || GM_config.isOpen) && conf !== false)
		text = Math.floor(Math.random() * 99) + 1;


	if (isNaN(text))
		text = 0;

	if (prev != text && (data = drawText(text)))
	{
		link.href = data;
		prev = text;
	}
	if (!nav)
	{
		nav = document.getElementById("nav-dropdown");
		if (nav)
		{
			let li = document.createElement("li"),
					lis = nav.getElementsByTagName("li"),
					a = document.createElement("a");
			for(let i = lis.length - 1; i >= 0; i--)
			{
				let c = lis[i];
				if (c.className == "divider")
				{
					a.innerText = "Plex Badge Config";
					a.href = "#";
					a.addEventListener("click", function(e)
					{
						e.preventDefault();
						GM_config.open();
					}, false);
					li.appendChild(a);
					c.parentNode.insertBefore(c.cloneNode(false), c);
					c.parentNode.insertBefore(li, c);
					break;
				}
			}
		}
	}
	if (!isConf)
		loop.timer = setTimeout(loop, 3000);
}
prefsUpdate();
loop();