// ==UserScript==
// @name YouTube ScreenShoter
// @namespace http://tampermonkey.net/
// @version 1.0.4
// @description Screenshots current YT frame in full quality
// @author DoctorDeathDDracula & Sticky
// @match *://*.youtube.com/*
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant none
// @run-at document-start
// @noframes
// ==/UserScript==
(function() {
'use strict';
function YouTubeScreenShoter() {}
YouTubeScreenShoter.prototype.storage = {
selectors: {
reactionButton: 'ytd-toggle-button-renderer',
screenshotButton: "#__my-screenshot-button",
reactionPanel: "#top-level-buttons-computed"
},
ids: {
screenshotButton: '__my-screenshot-button'
},
icons: {
iconBlackSVG: '<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="iconB.svg" inkscape:version="1.0 (4035a4fb49, 2020-05-01)" id="svg8" version="1.1" viewBox="0 0 6.3499999 6.35" height="24" width="24"><defs id="defs2"/><sodipodi:namedview inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:window-height="1017" inkscape:window-width="1920" units="px" showgrid="false" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" inkscape:cy="9.5831076" inkscape:cx="11.629344" inkscape:zoom="22.4" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#ffffff" id="base"/><metadata id="metadata5"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g inkscape:label="Слой 2" id="layer2" inkscape:groupmode="layer"/><g id="layer1" inkscape:groupmode="layer" inkscape:label="Слой 1"><path id="path10" d="m 0.86839843,2.5970013 0.007815,-1.72490613 1.71861757,0.006879" style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><path style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 5.4816006,2.5933023 5.4737906,0.8684026 3.7551742,0.8752816" id="path10-5"/><path style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 0.86810411,3.7567748 0.007815,1.7214547 1.71884519,-0.00686" id="path10-55"/><path id="path10-5-2" d="m 5.4819176,3.7604659 -0.00781,1.7214488 -1.7188442,-0.00685" style="fill:none;stroke:#000;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/></g></svg>',
iconWhiteSVG: '<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="iconW.svg" inkscape:version="1.0 (4035a4fb49, 2020-05-01)" id="svg8" version="1.1" viewBox="0 0 6.3499999 6.35" height="24" width="24"><defs id="defs2"/><sodipodi:namedview inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:window-height="1017" inkscape:window-width="1920" units="px" showgrid="false" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" inkscape:cy="9.5831076" inkscape:cx="11.629344" inkscape:zoom="22.4" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#ffffff" id="base"/><metadata id="metadata5"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title></dc:title></cc:Work></rdf:RDF></metadata><g inkscape:label="Слой 2" id="layer2" inkscape:groupmode="layer"/><g id="layer1" inkscape:groupmode="layer" inkscape:label="Слой 1"><path id="path10" d="m 0.86839843,2.5970013 0.007815,-1.72490613 1.71861757,0.006879" style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><path style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 5.4816006,2.5933023 5.4737906,0.8684026 3.7551742,0.8752816" id="path10-5"/><path style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 0.86810411,3.7567748 0.007815,1.7214547 1.71884519,-0.00686" id="path10-55"/><path id="path10-5-2" d="m 5.4819176,3.7604659 -0.00781,1.7214488 -1.7188442,-0.00685" style="fill:none;stroke:#fff;stroke-width:.52916667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/></g></svg>'
},
styles: '.__my-screenshot-animation {opacity: 0 !important}'
};
YouTubeScreenShoter.prototype.init = function() {
document.addEventListener('DOMContentLoaded', this.onDOMContentLoaded.bind(this));
this.setTriggerOnElement(this.storage.selectors.reactionPanel, 'addedNodes', this.onNewPanel.bind(this));
};
YouTubeScreenShoter.prototype.cc = function(tag, options, parent, init) {
options = options || {};
var children = options.children || [];
delete options.children;
var element = Object.assign(document.createElement(tag), options);
for (var i = 0; i < children.length; i++) element.appendChild(children[i]);
if (typeof init == 'function') init.call(element);
return parent && parent.nodeType === Node.ELEMENT_NODE ? parent.appendChild(element) : element;
};
YouTubeScreenShoter.prototype.ss = function(selector, searchIn, all) {
searchIn = searchIn || document;
return all ? searchIn.querySelectorAll(selector) : searchIn.querySelector(selector);
};
YouTubeScreenShoter.prototype.onDOMContentLoaded = function() {
this.addStyles();
};
YouTubeScreenShoter.prototype.addStyles = function() {
this.cc('style', {
textContent: this.storage.styles
}, document.head);
};
YouTubeScreenShoter.prototype.onNewPanel = function(panel) {
this.initPanel(panel);
this.setTriggerOnElement(this.storage.selectors.screenshotButton, 'removedNodes', (function() {
this.initPanel(panel);
}).bind(this), false, panel);
};
YouTubeScreenShoter.prototype.initPanel = function(panel) {
if (panel.children.length !== 0) {
var myButton = this.ss(this.storage.selectors.screenshotButton, panel);
if (!myButton) {
var reactionButton = this.ss(this.storage.selectors.reactionButton, panel);
if (reactionButton && reactionButton.nextElementSibling) {
reactionButton.nextElementSibling.after(this.generateButton());
}
}
}
};
YouTubeScreenShoter.prototype.setTriggerOnElement = function(selector, action, callback, once, searchIn) {
var observer = new MutationObserver(function(mutations) {
for (var mi = 0; mi < mutations.length; mi++) {
var mutation = mutations[mi];
var addedNodes = mutation[action] || [];
for (var ani = 0; ani < addedNodes.length; ani++) {
var node = addedNodes[ani];
var element = node.matches && node.matches(selector) ? node : (node.querySelector ? node.querySelector(selector) : null);
if (element) {
if (once) {
observer.disconnect();
return callback(element);
} else {
callback(element);
}
}
}
}
});
observer.observe(searchIn || document, {
attributes: false,
childList: true,
subtree: true
});
return observer;
};
YouTubeScreenShoter.prototype.generateButton = function() {
return this.cc("button", {
id: this.storage.ids.screenshotButton,
style: "appearance:none;border:none;outline:none;background-color:#0000;padding:0;display:flex;cursor:pointer;",
children: [
this.cc("div", {
style: "padding:6px;",
children: [
this.SVGPacker(document.firstElementChild.getAttribute("dark") == 'true' ? this.storage.icons.iconWhiteSVG : this.storage.icons.iconBlackSVG)
]
}),
this.cc("div", {
children: [
this.cc("div", {
style: this.packInlineStyle(this.getNoneDefaultStyle(this.ss("#text", this.ss(this.storage.selectors.reactionButton)))),
textContent: "PRTSC"
}, false, function() {
this.style.width = "";
this.style.padding = "10px 10px 0px 0px";
})
]
})
],
onclick: (function(event) {
var video = this.ss("video");
var canvas = this.cc("canvas", {
width: video.videoWidth,
height: video.videoHeight
});
var context = canvas.getContext('2d');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
if (event.ctrlKey) {
this.cc("a", {
download: "download",
href: canvas.toDataURL("image/png")
}).click();
} else {
if ('ClipboardItem' in window) {
canvas.toBlob(this.onCopyImage.bind(this));
} else {
alert('Sorry, your browser does not support coping, try to download it by pressing screenshot button with ctrl key pressed.')
}
}
}).bind(this)
});
};
YouTubeScreenShoter.prototype.onCopyImage = function(blob) {
navigator.clipboard.write([new window.ClipboardItem({
"image/png": blob
})]);
};
YouTubeScreenShoter.prototype.getComputedStyle = window.getComputedStyle.bind(window);
YouTubeScreenShoter.prototype.getNoneDefaultStyle = function(node) {
var styles = [];
var supportElement = this.cc(node.tagName, {
visible: false
}, document.body);
var elementStyles = this.getComputedStyle(node);
var defaultStyles = this.getComputedStyle(supportElement);
var keys = Object.keys(defaultStyles);
for (var i = 0; i < keys.length; i++) {
if (elementStyles[keys[i]] !== defaultStyles[keys[i]] && defaultStyles[keys[i]] !== '') styles.push([keys[i], elementStyles[keys[i]]]);
}
supportElement.remove();
return styles;
};
YouTubeScreenShoter.prototype.packInlineStyle = function(style) {
return style.reduce(function(pre, cur) {
return pre + ";" + cur[0] + ":" + cur[1];
}, "") + ";";
};
YouTubeScreenShoter.prototype.SVGPacker = function(SVGXML) {
return this.cc("div", {
innerHTML: SVGXML
}).firstChild;
};
var YTSS = new YouTubeScreenShoter();
YTSS.init();
})();