// ==UserScript==
// @name YouTube Embedder
// @namespace https://greasyfork.org/en/users/85671-jcunews
// @description Convert links and optionally URL text which points to a YouTube video, into embedded YouTube frame. For URL text, it can be converted to a link only. The main function can be configured for on-demand-only for slow computers. If enabled, the main function can be executed via bookmarklet using this URL: javascript:yte_ujs()
// @author jcunews
// @version 1.0.6
// @license GNU AGPLv3
// @match *://*/*
// @exclude *://www.youtube.com/embed/*
// @grant none
// ==/UserScript==
(function(xe, fe, ce, rx, trx, trxg, hrx, lo, qu) {
//===== CONFIGURATION BEGIN =====
//The minimum width & height of the container element where the link or URL text is found, in order to apply the conversion.
var minimumWidth = 256;
var minimumHeight = 144;
//Embedded YouTube frame width in pixels (should be multiplication of 16).
//If zero, the width is the container element width of the link or URL text, and the height is calculated using 16:9 ratio (widescreen video dimension).
//If the final width is smaller than the minimum width setting, and if the source is an URL text, it will be converted to a link instead.
var frameWidth = 0;
//Enable URL text to YouTube frame converter (aside from links).
var convertUrlText = true;
//Convert to link only, when on these domains.
var convertToLinkOnlyDomains = /somesite\.info|subnet\d+\.other\.net/i;
//Delay between processing each node scanning queue. Lower value means faster scanning, but more CPU intensive.
//Higher value means lesser CPU usage, but also means slower scanning.
var queueDelay = 50; //in milliseconds. 1000ms = 1 second.
//Number of nodes to process at a time. Adjust this and queueDelay settings to find the best performance without triggering the web browser's busy dialog.
//Lower value means lesser CPU usage, but also means slower scanning.
var nodesPerProcessing = 5;
//The onDemandOnlyDomains makes the main function available on-demand-only for specific or all sites.
//If enabled, it's accessible via bookmarklet using this URL: javascript:yte_ujs()
//This setting is for slow computers, since the main function is CPU intensive.
var onDemandOnlyDomains = /somesite\.info|subnet\d+\.other\.net/i;
//===== CONFIGURATION END =====
function linkToFrame(node, m, pn, w, h, a) {
if (node.ytebd_nocovert || !node.offsetWidth || !node.offsetHeight) return;
if (!lo && (m = node.href.match(rx))) {
m = "https://www.youtube.com/embed/" + m[1];
pn = node.parentNode;
while (pn) {
if (pn.scrollHeight > pn.offsetHeight) break;
pn = pn.parentNode;
}
h = ((pn || node.parentNode).offsetHeight * 0.9) >> 0;
w = frameWidth > 0 ? frameWidth : node.parentNode.offsetWidth;
if ((pn = (w / 16 * 9) >> 0) > h) {
w = (h / 9 * 16) >> 0;
} else h = pn;
if ((w >= minimumWidth) && (h >= minimumHeight)) {
c = document.createElement("IFRAME");
c.src = m;
c.allowFullscreen = true;
c.referrerPolicy = "no-referrer";
c.style.border = "none";
c.width = w;
c.height = h;
c.ytebd_nocovert = 1;
node.replaceWith(c);
} else if (w && h) node.ytebd_nocovert = 1;
} else node.ytebd_nocovert = 1;
}
function processNode(node, c, m, w, h, pn) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
case Node.DOCUMENT_NODE:
if (node.ytebd_nocovert) break;
if (node.nodeName === "A") {
linkToFrame(node);
node = null;
}
if (node && (xe.indexOf(node.nodeName) < 0)) {
if (fe.indexOf(node.nodeName) >= 0) {
m = (m = node.src.match(/:\/\/(.*?)\//)) && (m[1] === location.hostname);
} else m = true;
if (m && ((node.nodeName !== "A") || convertUrlText)) {
node.childNodes.forEach(queue);
}
}
break;
case Node.TEXT_NODE:
if (convertUrlText && (m = node.nodeValue.match(trx))) {
w = node.nodeValue;
h = -1;
a = [];
while (c = trxg.exec(w)) {
if (c.index > 0) a.push(w.substring(h, c.index));
a.push(c);
h = c.index + c[0].length;
}
if ((h > 0) && (h < w.length)) a.push(w.substr(h));
for (c = a.length - 1; c >= 0; c--) {
if (!Array.isArray(a[c]) && !a[c].replace(/^\s+|\s+$/g, "")) a.splice(c, 1);
}
if (node.parentNode.nodeName === "A") {
if (a.length === 1) {
node.href = "https://www.youtube.com/embed/" + a[0][1];
node.rel = "nofollow noopener noreferrer";
node.target = "_blank";
}
a = null;
}
if (a) {
pn = node.parentNode;
c = node.nextSibling;
a.forEach(function(v, i, n) {
if (!Array.isArray(v)) {
n = document.createTextNode(v);
} else {
n = document.createElement("A");
n.textContent = v[0];
n.title = v[1];
n.href = "https://www.youtube.com/embed/" + v[1];
n.rel = "nofollow noopener noreferrer";
n.target = "_blank";
}
if (i > 0) {
pn.insertBefore(n, c);
} else {
node.replaceWith(n);
}
linkToFrame(n);
});
}
}
}
}
function processQueue() {
setTimeout(function(nodes) {
nodes.forEach(processNode);
if (qu.length) setTimeout(processQueue, queueDelay);
}, queueDelay, qu.splice(0, nodesPerProcessing));
}
function queue(node) {
qu.push(node);
if (qu.length === 1) setTimeout(processQueue, queueDelay);
}
xe = ["BUTTON", "INPUT", "SCRIPT", "SELECT", "STYLE"];
fe = ["FRAME", "IFRAME"];
rx = /^(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})/i;
trx = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/i;
trxg = /(?:https?:\/\/)?(?:(?:(?:www\.)?youtube\.com\/)(?:embed\/|watch\?.*v=)|youtu\.be\/)([0-9a-z_\-]{11})[^\s,'")\]}>]*/gi;
hrx = /^(?:(?:www\.)?youtube\.com|youtu\.be)$/i;
lo = convertToLinkOnlyDomains.test(location.hostname);
qu = [];
if (nodesPerProcessing <= 0) nodesPerProcessing = 1;
if (!document.body) return;
if (onDemandOnlyDomains.test(location.hostname)) {
window.yte_ujs = function() {
queue(document.body);
return undefined;
};
} else {
if (document.body) queue(document.body);
(new MutationObserver(function(records) {
records.forEach(function(record) {
if (record.type === "childList") {
record.addedNodes.forEach(queue);
} else queue(record.target);
});
})).observe(document.body, {attributes: true, attributeFilter: ["class", "style"], childList: true, subtree: true});
}
})();