Youtube polymer engine fixes

Some fixes for Youtube polymer engine

目前為 2020-07-13 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Youtube polymer engine fixes
// @description  Some fixes for Youtube polymer engine
// @namespace    bo.gd.an[at]rambler.ru
// @version      0.9.0
// @match        https://www.youtube.com/*
// @compatible   firefox 56
// @author       Bogudan
// @grant        none
// @run-at       document-end
// ==/UserScript==

//debug helper:
//window.YTFixes = {};
(function() {
    'use strict';
	// test local storage availability (required for settings saving) and load settings
	var settings = {}, ls, styles = [], intervals = [];
	function lsTest (st, v) {
		st.setItem ('__fix_test__', v);
		return st.getItem ('__fix_test__') == v;
	    };
	try {
		var _s = window.localStorage;
		if (lsTest (_s, 'qwe') && lsTest (_s, 'rty')) { // do 2 times just in case LS stored value once, but does not let change it later
			ls = _s;
			settings = JSON.parse (ls.getItem ('__fix__settings__')) || {};
		    }
	    }
	catch (e) { }
	// settings
	var oldsettings = {
		"default_player_640": { conv: function (X) { settings ["default_player"] = X ? 3 : 0; }, def: false },
		"reduce_thumbnail": { conv: function (X) { settings ["thumbnail_size"] = X ? 2 : 0; }, def: true },
	    };
	for (var S in oldsettings)
	    if (S in settings) {
			var p = oldsettings [S];
			p.conv (settings [S]);
			delete settings [S];
		    }
	console.log ('fix settings:', settings);
	function getSetting (nm, def) { return nm in settings ? settings [nm] : def; }
	function addInterval (period, func, params) {
		if (!period)
			period = 1;
		intervals.push ({ cnt: period, period: period, call: func, params: params || [] });
	    }
	var size_norm, size_theater;
	var presettings = {
		"hide_guide": {
			type: "bool",
			def: true,
			desc: "Hide \"Guide\" menu when page opens",
			act: (X) => {
				if (!X) return;
				var guide_button = document.getElementById ('guide-button');
				if (!guide_button) return;
			    var tmp = guide_button.getElementsByTagName ('button');
				if (!tmp.length) return;
				tmp = tmp [0].attributes;
				if (tmp && tmp ['aria-pressed'].value == 'true')
					guide_button.click ();
		        }
			},
		"reduce_font": {
			type: "bool",
			def: true,
			desc: "Reduce font size in video descriptions",
			act: (X) => {
				if (!X) return;
				styles.push ('#video-title.ytd-rich-grid-video-renderer,#text.ytd-channel-name,#metadata-line.ytd-video-meta-block{font-size:14px!important;line-height:1.2em!important}');
				// fix: "not interested" placeholder bigger than original element by about 40%
				styles.push ('paper-button.style-blue-text{padding:0!important}');
			    }
		    },
		"thumbnail_size": {
			type: "list",
			def: 2,
			desc: "Set thumbnails size (front page)",
			opts: ['default', '180px', '240px', '360px', '480px'],
			act: (X) => {
				if (!X) return;
				var w = [0, 180, 240, 360, 480] [X];
				styles.push ('ytd-rich-item-renderer{width:' + w + 'px!important}');
			    }
		    },
		"default_player": {
			type: "list",
			def: 0,
			desc: "Set player size in default mode",
			opts: ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px'],
			act: (X) => {
				if (!X) return;
				var w = [0, 256, 427, 640, 854, 1280] [X];
				var h = [0, 144, 240, 360, 480, 720] [X];
				styles.push ('#player-container-outer{min-width:' + w + 'px!important;max-width:' + w + 'px!important}');
				size_norm = [w, h];
			    }
		    },
		"theater_player": {
			type: "list",
			def: 0,
			desc: "Set player size in theater mode",
			opts: ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px'],
			act: (X) => {
				if (!X) return;
				var w = [0, 256, 427, 640, 854, 1280] [X];
				var h = [0, 144, 240, 360, 480, 720] [X];
				styles.push ('#player-theater-container{min-width:' + w + 'px!important;max-width:' + w + 'px!important;max-height:' + h + 'px!important}');
				size_theater = [w, h];
			    }
		    },
		"hide_yt_suggested_blocks": {
			type: "bool",
			def: true,
			desc: "Hide YouTube suggestions blocks like \"Recommended playlists\" or \"Latest YouTube posts\"",
			act: (X) => {
				if (X)
					styles.push ('div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}');
			    }
		    },
		"unfix_header": {
			type: "bool",
			def: true,
			desc: "Unstick header bar from top of the screen",
			act: (X) => {
				if (X)
					styles.push ('div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app{position:absolute!important}');
			    }
		    },
		"align_player": {
			type: "list",
			def: 0,
			desc: "Align resized player into it's container (normal and theater modes)",
			opts: ['center (default)', 'left', 'right'],
			act: (X) => {
				if (X)
					styles.push ('#player-container-outer{margin-' + ['', 'left', 'right'] [X] + ':0!important}');
			    }
		    },
		"logo_target": {
			type: "text",
			def: "",
			desc: "Change YT logo target to https://www.youtube.com/...",
			act: (X) => {
				if (!X) return;
				if (X [0] != '/')
					X = '/' + X;
				X = document.location.origin + X;
				addInterval (1, function (X) {
					var l = document.querySelectorAll ('a#logo');
					for (var i = l.length; --i >= 0; )
						if (l [i].href != X) {
							l [i].href = X;
							l [i].data.commandMetadata.webCommandMetadata.url = X;
						    }
				    }, [X]);
			    }
		    },
	    };
	// catch "settings" page
    if (document.location.pathname == '/fix-settings') {
		document.title = "YouTube Polymer Fixes: Settings";
		var plane = document.createElement ('div'), e1, e2, e3;
		plane.setAttribute ('style', 'position:absolute;left:0;top:0;right:0;bottom:0;background:#eee;padding:3em');
		function addLine () {
			var q = document.createElement ('div');
			for (var i = 0, L = arguments.length; i < L; ++i)
				q.appendChild (arguments [i]);
			q.setAttribute ('style', 'margin:1em');
			plane.appendChild (q);
		    }
		e1 = document.createElement ('b');
		e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
		addLine (e1);
		if (!ls) {
			e1 = document.createElement ('span');
			e1.style = 'color:red';
			e1.appendChild (document.createTextNode ('Cannot edit settings: no access to local storage.'));
			e2 = document.createElement ('span');
			e2.appendChild (document.createTextNode ('If you using Firefox, allow cookies for this site.'));
			addLine (e1, e2);
		    }
		else {
			for (var S in presettings) {
				var p = presettings [S];
				var val = getSetting (S, p.def);
				if (p.type == "bool") {
					e1 = document.createElement ('input');
					e1.setAttribute ('type', 'checkbox');
					e1.setAttribute ('style', 'margin-right: 1em;');
					if (val)
						e1.setAttribute ('checked', '');
					e1.name = '__fix__' + S;
					e2 = document.createElement ('span');
					e2.appendChild (document.createTextNode (p.desc));
					addLine (e1, e2);
				    }
				if (p.type == "list") {
					e1 = document.createElement ('span');
					e1.appendChild (document.createTextNode (p.desc));
					e1.setAttribute ('style', 'margin-right: 1em;');
					e2 = document.createElement ('select');
					for (var i = 0, L = p.opts.length; i < L; ++i) {
						e3 = e2.appendChild (document.createElement ('option'));
						e3.appendChild (document.createTextNode (p.opts [i]));
						if (i == val)
							e3.setAttribute ('selected', '');
					    }
					e2.setAttribute ('style', 'padding: 0.2em;border:1px solid #888');
					e2.name = '__fix__' + S;
					addLine (e1, e2);
				    }
				if (p.type == "text") {
					e1 = document.createElement ('span');
					e1.appendChild (document.createTextNode (p.desc));
					e1.setAttribute ('style', 'margin-right: 1em;');
					e2 = document.createElement ('input');
					e2.value = val;
					e2.setAttribute ('style', 'padding: 0.2em;border:1px solid #888');
					e2.name = '__fix__' + S;
					addLine (e1, e2);
				    }
			    }
			e1 = document.createElement ('input');
			e1.setAttribute ('type', 'button');
			e1.setAttribute ('style', 'padding:0.4em;border:1px solid #888');
			e1.value = 'Save settings and return to YouTube';
			e1.addEventListener ('click', function () {
				settings = {};
				for (var S in presettings) {
					var p = presettings [S];
					var e = document.getElementsByName ('__fix__' + S);
					if (p.type == "bool")
						settings [S] = e [0].checked; // checkbox
					if (p.type == "list")
						settings [S] = e [0].selectedIndex; // drop-down
					if (p.type == "text")
						settings [S] = e [0].value; // text box
				    }
				ls.setItem ('__fix__settings__', JSON.stringify (settings));
				alert ('Settings saved');
				history.back ();
			    });
			e2 = document.createElement ('input');
			e2.setAttribute ('type', 'button');
			e2.setAttribute ('style', 'padding:0.4em;border:1px solid #888');
			e2.value = 'Return to YouTube without saving';
			e2.addEventListener ('click', function () {
				history.back ();
			    });
			addLine (e1, document.createTextNode (' '), e2);
		    }
		document.body.appendChild (plane);
		return;
	    }
	// "settings" button
	// can't store created button: Polymer overrides it's content on soft reload leaving tags in place
	// but can store element that Polymer does not know how to deal with and just drops
	var settingsButtonMark;
	function createSettingsButton () {
		if (settingsButtonMark && settingsButtonMark.parentNode)
			return;
		var toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
		var _1st = toolBar [0];
		if (!_1st)
			return;
		toolBar = _1st.parentNode;
		var sb = document.createElement ('ytd-topbar-menu-button-renderer');
		sb.className = 'style-scope ytd-masthead style-default';
		sb.setAttribute ('use-keyboard-focused', '');
		sb.setAttribute ('is-icon-button', '');
		sb.setAttribute ('has-no-text', '');
		toolBar.insertBefore (sb, toolBar.childNodes [0]);
		var mark = document.createElement ('fix-settings-mark');
		mark.setAttribute ('style', 'display:none');
		toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
		var icb = document.createElement ('yt-icon-button');
		icb.id = 'button';
		icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
		var aa = document.createElement ('a');
		aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
		aa.setAttribute ('tabindex', '-1');
		aa.setAttribute ('href', '/fix-settings');
		aa.appendChild (icb);
		sb.getElementsByTagName ('div') [0].appendChild (aa); // created by YT scripts
		var bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
		bb.setAttribute ('aria-label', 'fixes settings');
		var ic = document.createElement ('yt-icon');
		ic.className = 'style-scope ytd-topbar-menu-button-renderer';
		bb.appendChild (ic);
		var gpath = document.createElementNS ('http://www.w3.org/2000/svg', 'path');
		gpath.className.baseVal = 'style-scope yt-icon';
		gpath.setAttribute ('d', 'M1 20l6-6h2l11-11v-1l2-1 1 1-1 2h-1l-11 11v2l-6 6h-1l-2-2zM13 15l2-2 8 8v1l-1 1h-1zM9 11l2-2-2-2 1.5-3-3-3h-2l3 3-1.5 3-3 1.5-3-3v2l3 3 3-1.5z');
		var svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
		svgg.className.baseVal = 'style-scope yt-icon';
		svgg.appendChild (gpath);
		var svg = document.createElementNS ('http://www.w3.org/2000/svg', 'svg');
		svg.className.baseVal = 'style-scope yt-icon';
		svg.setAttributeNS (null, 'viewBox', '0 0 24 24');
		svg.setAttributeNS (null, 'preserveAspectRatio', 'xMidYMid meet');
		svg.setAttribute ('focusable', 'false');
		svg.setAttribute ('style', 'pointer-events: none; display: block; width: 100%; height: 100%;');
		svg.appendChild (svgg);
		ic.appendChild (svg); // YT clears *ic
		settingsButtonMark = mark;
	    }
    addInterval (1, createSettingsButton, []);
	// apply settings
	for (var S in presettings) {
		var p = presettings [S];
		p.act (getSetting (S, p.def));
	    }
	if (size_norm || size_theater)
		addInterval (1, function (sn, st) {
			var eq = document.getElementsByTagName ("ytd-watch-flexy");
			if (!eq.length)
				return;
			var s = eq [0].hasAttribute ('theater') ? st : sn;
			if (!s)
				return;
			var ep = document.getElementById ("movie_player");
			if (ep && ep.setInternalSize && ep.isFullscreen && !ep.isFullscreen () && ep.getPlayerSize && ep.getPlayerSize ().width != s [0])
				ep.setInternalSize (s [0], s [1]);
		    }, [size_norm, size_theater]);
	// styles
	addInterval (1, function () {
		if (styles.length == 0 || !document.body) return;
	    var style_element = document.createElement ('style');
	    style_element.type = 'text/css';
	    style_element.innerHTML = styles.join ('');
		document.body.appendChild(style_element);
		styles = [];
	    }, []);
	setInterval (function () {
		for (var i = intervals.length; --i >= 0; ) {
			var Q = intervals [i];
			if (--Q.cnt > 0)
				continue;
			Q.call.apply (this, Q.params);
			Q.cnt = Q.period;
		    }
	    }, 1000);
    })();