// ==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== // @author V@no // @version 2.0 // @grant none // @downloadURL none // ==/UserScript== var prefs = { 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: 0, //image size in pixels, 0 = original }; var links = document.getElementsByTagName("link"), link = null, head = document.getElementsByTagName('head')[0], prev = null, img = new Image(), canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), size = 16, multi; for(let i = 0; i < links.length; i++) { if (links[i].getAttribute("rel") == "shortcut icon") { link = links[i]; break; } } if (!link) { link = document.createElement('link'); head.appendChild(link); /* //for debuging purposes link = document.createElement('img'); document.body.appendChild(link); let span = document.createElement("span"); var auto = false,add = 0; span.className = "activity-badge badge badge-transparent"; span.innerText = "0"; span.style.display = "none"; document.body.appendChild(span); link.addEventListener("click", function(e){if (e.button)return;add = 1;}, false); link.addEventListener("dblclick", function(e){auto = !auto;}, false); */ link.type = 'image/x-icon'; link.rel = 'shortcut icon'; link.href = "/web/favicon.ico"; } img.setAttribute('crossOrigin','anonymous'); img.src = link.href; img.onload = function() { if (prefs.sizeIcon) img.width = img.height = prefs.sizeIcon; img.loaded = true; size = img.height; multi = prefs.textSize ? prefs.textSize : size / 16; canvas.width = canvas.height = size; }; function drawText(text) { if (!img.loaded) return; ctx.save(); ctx.drawImage(img, 0, 0, size, size); if (text) { let 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(); 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; } //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() { let data, _badge = document.getElementsByClassName("activity-badge badge badge-transparent"), badge = _badge.length ? _badge[0].innerText : ""; //try{if(add)_badge[0].innerText++; if(!auto) add = 0}catch(e){}; let text = parseInt(badge); if (isNaN(text)) text = 0; if (prev != text && (data = drawText(text))) { //link.src = data; link.href = data; prev = text; } setTimeout(loop, 1000); })();