Greasy Fork

Greasy Fork is available in English.

Youtube polymer engine fixes

Some fixes for Youtube polymer engine

当前为 2021-04-12 提交的版本,查看 最新版本

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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      2.5.7
// @match        https://www.youtube.com/*
// @compatible   firefox 56
// @author       Bogudan
// @grant        GM_info
// @grant        GM.info
// @grant        GM_getValue
// @grant        GM.getValue
// @grant        GM_setValue
// @grant        GM.setValue
// @noframes
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';
	let gminfo = typeof (GM_info) !== 'undefined' && GM_info || typeof (GM) !== 'undefined' && GM && GM.info;
	let fix_version = gminfo && gminfo.script && gminfo.script.version;
	if (document.location.pathname == '/error')	// there is nothing to do on error page
		return;
	// test local storage availability and load settings from there first
	let settings, ls, saver;
	try {
		function lsTest (st, v) {
			st.setItem ('__fix_test__', v);
			return st.getItem ('__fix_test__') == v;
			};
		let _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;
			ls.removeItem ('__fix_test__');
			settings = JSON.parse (ls.getItem ('__fix__settings__'));
			}
		}
	catch (e) { }
	// select storage: GM_*/GM.* or local storage (in case userscript manager does not grant us GMs)
	if (typeof (GM_getValue) !== 'undefined' && typeof (GM_setValue) !== 'undefined' && GM_getValue && GM_setValue) {
		saver = function () {
			GM_setValue ('settings', settings);
			if (ls)
				ls.removeItem ('__fix__settings__');
			}
		if (!settings)
			settings = GM_getValue ('settings', {});
		else
			saver ();
		settings.storage = 'GM_*Value';
		}
	else if (typeof (GM) !== 'undefined' && GM && GM.getValue && GM.setValue) {
		saver = function () {
			(async () => await GM.setValue ('settings', settings)) ();
			if (ls)
				ls.removeItem ('__fix__settings__');
			};
		if (!settings)
			settings = (async () => await GM.getValue ('settings', {})) ();
		else
			saver ();
		settings.storage = 'GM.*Value';
		}
	else if (ls) {
		if (!settings)
			settings = {};
		saver = function () {
			ls.setItem ('__fix__settings__', JSON.stringify (settings));
			};
		settings.storage = 'window.localStorage.*Item';
		}
	else
		settings = {};
	// delete old settings
	if ("default_player_640" in settings) {
		settings.default_player = settings.default_player_640 ? 3 : 0;
		delete settings.default_player_640;
		}
	if ("reduce_thumbnail" in settings) {
		settings.thumbnail_size = settings.reduce_thumbnail ? 2 : 0;
		delete settings.reduce_thumbnail;
		}
	// set default values
	settings.version = fix_version;
	if (!("inst_ver" in settings))
		settings.inst_ver = fix_version;
	if (!("align_player" in settings))
		settings.align_player = 0;
	if (!("default_player" in settings))
		settings.default_player = 0;
	if (!("hide_guide" in settings))
		settings.hide_guide = true;
	if (!("hide_yt_suggested_blocks" in settings))
		settings.hide_yt_suggested_blocks = true;
	if (!("logo_target" in settings))
		settings.logo_target = "";
	if (!("reduce_font" in settings))
		settings.reduce_font = true;
	if (!("theater_player" in settings))
		settings.theater_player = 0;
	if (!("thumbnail_size" in settings))
		settings.thumbnail_size = 2;
	if (!("thumbnail_size_m" in settings))
		settings.thumbnail_size_m = 720;
	if (!("unfix_header" in settings))
		settings.unfix_header = true;
	if (!("search_thumbnail" in settings))
		settings.search_thumbnail = 0;
	if (!("clear_search" in settings))
		settings.clear_search = false;
	if (!("channel_top" in settings))
		settings.channel_top = 0;
	if (!("try_load_more" in settings))
		settings.try_load_more = false;
	if (!("unbound_video_title" in settings))
		settings.unbound_video_title = false;
	if (!("video_quality" in settings))
		settings.video_quality = 0;
	if (!("no_resume_time" in settings))
		settings.no_resume_time = false;
	console.log ('fix settings:', settings);
	// catch "settings" page
	if (document.location.pathname == '/fix-settings') {
		document.title = "YouTube Polymer Fixes: Settings";
		let back = document.createElement ('div');
		back.className = 'ytfixback';
		let plane = document.createElement ('div'), e1, e2;
		plane.className = 'ytfix';
		let style = document.createElement ('style');
		style.type = 'text/css';
		style.innerHTML = [
			'.ytfix{position:absolute;left:0;top:0;right:0;background:#eee;padding:3em}',
			'.ytfix_line{margin:1em}',
			'.ytfix_line span,.ytfix_line input,.ytfix_line select{margin-right:1em}',
			'.ytfix_field{padding:0.2em;border:1px solid #888}',
			'.ytfix_button{padding:0.4em;border:1px solid #888}',
			'.ytfix_hide{display:none}',
			'.ytfixback{position:absolute;left:0;top:0;right:0;height:100%;background:#eee}'
			].join ('');
		plane.appendChild (style);
		function AddLine () {
			let q = document.createElement ('div');
			q.className = 'ytfix_line';
			for (let i = 0, L = arguments.length; i < L; ++i)
				q.appendChild (arguments [i]);
			plane.appendChild (q);
			return q;
			}
		e1 = document.createElement ('b');
		e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
		AddLine (e1);
		if (!saver) {
			e1 = document.createElement ('span');
			e1.style = 'color:red';
			e1.appendChild (document.createTextNode ('Cannot edit settings: no access to any storage.'));
			AddLine (e1);
			e1 = document.createElement ('span');
			e1.appendChild (document.createTextNode ('If you are using Firefox, allow cookies for this site.'));
			AddLine (e1);
			}
		else {
			let ess = {};
			function MakeDesc (desc) {
				let e = document.createElement ('span');
				e.appendChild (document.createTextNode (desc));
				return e;
				}
			function MakeBoolElement (nm) {
				let e = document.createElement ('input');
				e.type = 'checkbox';
				e.checked = settings [nm];
				ess [nm] = e;
				return e;
				}
			function MakeListElement (nm, opts) {
				let e = document.createElement ('select');
				e.className = 'ytfix_field';
				ess [nm] = e;
				for (let i = 0, L = opts.length; i < L; ++i) {
					let o = document.createElement ('option');
					o.appendChild (document.createTextNode (opts [i]));
					//if (i == val)
					//	o.setAttribute ('selected', '');
					e.appendChild (o);
					}
				e.selectedIndex = settings [nm];
				return e;
				}
			function MakeTextElement (nm) {
				let e = document.createElement ('input');
				e.className = 'ytfix_field';
				e.value = settings [nm];
				ess [nm] = e;
				return e;
				}
			AddLine (MakeBoolElement ("hide_guide"), MakeDesc ('Hide "Guide" menu when page opens'));
			AddLine (MakeBoolElement ("reduce_font"), MakeDesc ("Reduce font size in video descriptions"));
			let tsm = MakeTextElement ("thumbnail_size_m");
			tsm.className = settings.thumbnail_size == 5 ? 'ytfix_field' : 'ytfix_hide';
			let tsi = MakeListElement ("thumbnail_size", ['default', '180px', '240px', '360px', '480px', 'manual']);
			tsi.addEventListener ('change', function () {
				ess.thumbnail_size_m.className = ess.thumbnail_size.selectedIndex == 5 ? 'ytfix_field' : 'ytfix_hide';
				});
			AddLine (MakeDesc ('Set thumbnails width for front page'), tsi, tsm);
			AddLine (MakeDesc ('Set thumbnails width for search page'), MakeListElement ("search_thumbnail", ['default', '240px', '360px']));
			AddLine (MakeDesc ("Set player size in default mode"), MakeListElement ("default_player", ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px']));
			AddLine (MakeDesc ("Set player size in theater mode"), MakeListElement ("theater_player", ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px']));
			AddLine (MakeBoolElement ("hide_yt_suggested_blocks"), MakeDesc ('Hide suggestions blocks on main page (recommended playlists, latest posts, etc.)'));
			AddLine (MakeBoolElement ("clear_search"), MakeDesc ("Hide suggestions blocks in search (for you, people also watched, etc.)"));
			AddLine (MakeBoolElement ("unfix_header"), MakeDesc ("Unstick header bar from top of the screen"));
			AddLine (MakeDesc ("Align resized player into it's container (normal and theater modes)"), MakeListElement ("align_player", ['center', 'left', 'right']));
			AddLine (MakeDesc ("Modify channels' pages behaviour"), MakeListElement ('channel_top', ['default', 'hide banner with scrolling', 'hide banner on load']));
			AddLine (MakeBoolElement ('try_load_more'), MakeDesc ('Add button to try loading more content on pages with dynamic content load'));
			AddLine (MakeBoolElement ('unbound_video_title'), MakeDesc ('Remove size limit for video titles'));
			AddLine (MakeDesc ("Change YT logo target to https://www.youtube.com/..."), MakeTextElement ("logo_target"));
			AddLine (MakeBoolElement ("no_resume_time"), MakeDesc ('Remove resume time from the video links (&t=...)'));
			AddLine (MakeDesc ('Starting video quality'), MakeListElement ('video_quality', ['Auto (default)', '2160p (4K)', '1440p (HD)', '1080p (HD)', '720p', '480p', '360p', '240p', '144p']));
			e1 = document.createElement ('input');
			e1.type = 'button';
			e1.className = 'ytfix_button';
			e1.value = 'Save settings and return to YouTube';
			e1.addEventListener ('click', function () {
				settings.hide_guide = ess.hide_guide.checked;
				settings.reduce_font = ess.reduce_font.checked;
				settings.thumbnail_size = ess.thumbnail_size.selectedIndex;
				if (settings.thumbnail_size == 5) {
					let v = ess.thumbnail_size_m.value;
					if (!/^\d+$/.test (v)) {
						alert ('Error: invalid value for thumbnails size');
						return;
						}
					settings.thumbnail_size_m = parseInt (v);
					}
				settings.search_thumbnail = ess.search_thumbnail.selectedIndex;
				settings.default_player = ess.default_player.selectedIndex;
				settings.theater_player = ess.theater_player.selectedIndex;
				settings.hide_yt_suggested_blocks = ess.hide_yt_suggested_blocks.checked;
				settings.unfix_header = ess.unfix_header.checked;
				settings.align_player = ess.align_player.selectedIndex;
				settings.channel_top = ess.channel_top.selectedIndex;
				settings.logo_target = ess.logo_target.value;
				settings.clear_search = ess.clear_search.checked;
				settings.try_load_more = ess.try_load_more.checked;
				settings.unbound_video_title = ess.unbound_video_title.checked;
				settings.video_quality = ess.video_quality.selectedIndex;
				settings.no_resume_time = ess.no_resume_time.checked;
				saver ();
				alert ('Settings saved');
				history.back ();
				});
			e2 = document.createElement ('input');
			e2.type = 'button';
			e2.className = 'ytfix_button';
			e2.value = 'Return to YouTube without saving';
			e2.addEventListener ('click', function () {
				history.back ();
				});
			AddLine (e1, e2);
			e1 = document.createElement ('input');
			e1.type = 'button';
			e1.className = 'ytfix_button';
			e1.value = 'Export settings';
			e1.addEventListener ('click', function () {
				let d = document.createElement ('a');
				d.style.display = 'none';
				d.setAttribute ('download', 'ytfix_settings.json');
				d.setAttribute ('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent (JSON.stringify (settings)));
				document.body.appendChild (d);
				d.click ();
				document.body.removeChild (d);
				});
			e2 = document.createElement ('input');
			e2.type = 'button';
			e2.className = 'ytfix_button';
			e2.value = 'Import settings';
			e2.addEventListener ('click', function () {
				let f = document.createElement ('input');
				f.type = 'file';
				f.style.display = 'none';
				f.addEventListener ('change', function () {
					if (f.files.length != 1)
						return;
					let rdr = new FileReader ();
					rdr.addEventListener ('load', function () {
						try {
							settings = JSON.parse (rdr.result);
							saver ();
							alert ('Settings imported');
							document.location.reload ();
							}
						catch (ex) {
							alert ('Error parsing settings\n' + ex);
							}
						});
					rdr.addEventListener ('error', () => alert ('Error loading file\n' + rdr.error));
					rdr.readAsText (f.files [0]);
					});
				document.body.appendChild (f);
				f.click ();
				document.body.removeChild (f);
				});
			AddLine (e1, e2);
			}
		let int = setInterval (function () {
			if (!document.body)
				return;
			document.body.appendChild (back);
			document.body.appendChild (plane);
			clearInterval (int);
			}, 1);
		console.log ('Settings page created');
		return;
		}
	// apply settings
	let styles = [], inject_func = [], inject_ints = [];
	if (settings.hide_guide) {
		function HideGuideTimer (info) {
			if (info.act == 0) {	// observe location change
				let url = document.location.toString ();
				if (url != info.url)
					info.act = 1;
				}
			if (info.act == 1) {	// wait for sorp page load completion
				let Q = document.getElementsByTagName ('yt-page-navigation-progress');
				if (!Q.length)
					return;
				if (Q [0].hasAttribute ('hidden'))
					info.act = 2;
				}
			if (info.act == 2) {	// wait for button and press it if necessary
				let guide_button = document.getElementById ('guide-button');
				if (!guide_button)
					return;
				let tmp = guide_button.getElementsByTagName ('button');
				if (!tmp.length)
					return;
				tmp = tmp [0];
				if (!tmp.hasAttribute ('aria-pressed'))
					return;
				if (tmp.attributes ['aria-pressed'].value == 'true')
					guide_button.click ();
				else {
					info.url = document.location.toString ();
					info.act = 0;
					window.dispatchEvent (new Event ('resize'));
					}
				}
			}
		inject_func.push (HideGuideTimer);
		inject_ints.push ({ call: HideGuideTimer, params: [{ act: 2 }] });
		}
	if (settings.reduce_font) {
		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}');
		styles.push ('tp-yt-paper-button.style-blue-text{padding:0!important}');
		}
	if (settings.thumbnail_size)
		styles.push ('ytd-rich-item-renderer{width:' + [0, 180, 240, 360, 480, settings.thumbnail_size_m] [settings.thumbnail_size] + 'px!important}');
	if (settings.hide_yt_suggested_blocks)
		styles.push ('div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}');
	if (settings.unfix_header) {
		styles.push ('div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app,app-drawer#guide{position:absolute!important}');
		styles.push ('ytd-feed-filter-chip-bar-renderer{position:relative!important}');
		styles.push ('div#chips-wrapper{position:absolute!important;top:0!important}');
		}
	if (settings.search_thumbnail) {
		let sz = [0, 240, 360] [settings.search_thumbnail] + 'px!important';
		// min-width defaults to 240px, max-width defaults to 360px
		// sizes for: videos, playlists, channels, mixes
		styles.push ('ytd-video-renderer[use-prominent-thumbs] ytd-thumbnail.ytd-video-renderer,ytd-playlist-renderer[use-prominent-thumbs] ytd-playlist-thumbnail.ytd-playlist-renderer,ytd-channel-renderer[use-prominent-thumbs] #avatar-section.ytd-channel-renderer,ytd-radio-renderer[use-prominent-thumbs] ytd-thumbnail.ytd-radio-renderer{min-width:' + sz + ';max-width:' + sz + '}');
		}
	if (settings.clear_search)
		styles.push ('ytd-two-column-search-results-renderer ytd-shelf-renderer.style-scope.ytd-item-section-renderer,ytd-two-column-search-results-renderer ytd-horizontal-card-list-renderer.style-scope.ytd-item-section-renderer{display:none!important}');
	styles.push ([
		'#player-theater-container{margin-left:auto!important;margin-right:auto!important}',
		'#player-container-outer{margin-left:0!important}',
		'#player-container-outer{margin-right:0!important}#player-theater-container{margin-left:auto!important}',
		] [settings.align_player]);
	const sizes = [undefined, { w: 256, h: 244 }, { w: 427, h: 240 }, { w: 640, h: 360 }, { w: 854, h: 480 }, { w: 1280, h: 720 }];
	let size_norm = sizes [settings.default_player];
	if (size_norm) {
		styles.push ('#player-container-outer{min-width:' + size_norm.w + 'px!important;max-width:' + size_norm.w + 'px!important}');
		styles.push ('#primary{min-width:' + size_norm.w + 'px!important;max-width:max(var(--ytd-watch-flexy-max-player-width),' + size_norm.w + 'px)!important}');
		}
	let size_theater = sizes [settings.theater_player];
	if (size_theater)
		styles.push ('ytd-watch-flexy:not([fullscreen]) #player-theater-container{min-width:' + size_theater.w + 'px!important;max-width:' + size_theater.w + 'px!important;max-height:' + size_theater.h + 'px!important}');
	if (size_norm || size_theater) {
		function SetPlayerSize (sn, st) {
			let eq = document.getElementsByTagName ("ytd-watch-flexy");
			if (!eq.length)
				return;
			let s = eq [0].hasAttribute ('theater') ? st : sn;
			if (!s)
				return;
			let ep = document.getElementById ("movie_player");
			if (ep && ep.setInternalSize && ep.isFullscreen && ep.getPlayerSize && !ep.isFullscreen () && ep.getPlayerSize ().width != s [0])
				ep.setInternalSize (s [0], s [1]);
		    }
		inject_func.push (SetPlayerSize);
		inject_ints.push ({ call: SetPlayerSize, params: [size_norm, size_theater] });
		}
	if (settings.logo_target) {
		let url = settings.logo_target;
		if (url [0] != '/')
			url = '/' + url;
		url = document.location.origin + url;
		function SetLogoURL (url) {
			let l = document.querySelectorAll ('a#logo');
			for (let i = l.length; --i >= 0; ) {
				let Q = l [i];
				let D = Q.data;
				if (D && D.commandMetadata && Q.href != url) {
					Q.href = url;
					D.commandMetadata.webCommandMetadata.url = url;
				    }
				}
		    }
		inject_func.push (SetLogoURL);
		inject_ints.push ({ call: SetLogoURL, params: [url] });
		}
	if (settings.channel_top)
		styles.push ('app-header#header.style-scope.ytd-c4-tabbed-header-renderer{transform:none!important;position:absolute;left:0px!important;top:0px;margin-top:0px}');
	if (settings.channel_top > 1) {
		styles.push ('div#contentContainer.style-scope.app-header-layout{padding-top:148px!important}');
		styles.push ('div#contentContainer.style-scope.app-header{height:148px!important}');
		styles.push ('div.banner-visible-area.style-scope.ytd-c4-tabbed-header-renderer{display:none!important}');
		}
	if (settings.try_load_more) {
		function TryLoadMore () {
			let l = document.querySelectorAll ('#show-more-button');
			let i = l.length;
			if (--i >= 0 && l [i].hasAttribute ('hidden')) {
				l [i].removeAttribute ('hidden');
				l [i].innerText = 'TRY LOAD MORE';
				}
			while (--i >= 0)
				l [i].parentNode.removeChild (l [i]);
			}
		inject_func.push (TryLoadMore);
		inject_ints.push ({ call: TryLoadMore });
		styles.push ('#show-more-button{color:var(--yt-spec-call-to-action);width:100%;text-align:center;border:1px solid;padding:1em;cursor:pointer}');
		}
	if (settings.unbound_video_title)
		styles.push ('#video-title{max-height:none!important}');
	if (settings.video_quality) {
		function TryQuality (quality, qq, ep) {
			return qq.includes (quality) && (ep.setPlaybackQualityRange (quality, quality) || true);
			}
		function UpdateVideoQuality (det, st) {
			let ep = document.getElementById ("movie_player");
			if (!ep || !ep.getPreferredQuality || !ep.getAvailableQualityLevels || !ep.setPlaybackQualityRange || !ep.getVideoData || ep.getPreferredQuality () != 'auto')
				return;
			let vid = ep.getVideoData ().video_id;
			if (st.fail == vid)	// last time on this video we've issues
				return;
			let qq = ep.getAvailableQualityLevels ();
			if (!qq || !qq.length)
				return;
			switch (det) {	// intentional no breaks here
				case 1: if (TryQuality ('hd2160', qq, ep)) return;
				case 2: if (TryQuality ('hd1440', qq, ep)) return;
				case 3: if (TryQuality ('hd1080', qq, ep)) return;
				case 4: if (TryQuality ('hd720', qq, ep)) return;
				case 5: if (TryQuality ('large', qq, ep)) return;
				case 6: if (TryQuality ('medium', qq, ep)) return;
				case 7: if (TryQuality ('small', qq, ep)) return;
				case 8: if (TryQuality ('tiny', qq, ep)) return;
				}
			console.log ('Unknown video qualities in list: ', qq);
			st.fail = vid;
			};
		inject_func.push (TryQuality);
		inject_func.push (UpdateVideoQuality);
		inject_ints.push ({ call: UpdateVideoQuality, params: [settings.video_quality, {}] });
		}
	if (settings.no_resume_time) {
		styles.push ('div.ytd-thumbnail-overlay-resume-playback-renderer{width:100%!important}');
		function removeTimes () {
			const links = document.querySelectorAll ('a[href*="&t="]');
			for (let i = links.length; --i >= 0; ) {
				const l = links [i];
				l.href = l.href.replace (/&t=\d+s?/, '');
				}
			}
		inject_func.push (removeTimes);
		inject_ints.push ({ call: removeTimes });
		}
	// "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
	function createSettingsButton (fix_version, st) {
		if (st.mark && st.mark.parentNode)
			return;
		let toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
		if (!toolBar.length)
			return;
		let _1st = toolBar [0];
		if (!_1st)
			return;
		toolBar = _1st.parentNode;
		let 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]);
		let mark = document.createElement ('fix-settings-mark');
		mark.style = 'display:none';
		toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
		st.mark = mark;
		let icb = document.createElement ('yt-icon-button');
		icb.id = 'button';
		icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
		let tt = document.createElement ('tp-yt-paper-tooltip');
		tt.className = 'style-scope ytd-topbar-menu-button-renderer';
		tt.setAttribute ('role', 'tooltip');
		tt.setAttribute ('tabindex', '-1');
		tt.style = 'right:auto;bottom:auto';
		tt.appendChild (document.createTextNode ('YT fixes ' + fix_version));	// YT wraps content into DIV element
		let aa = document.createElement ('a');
		aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
		aa.setAttribute ('tabindex', '-1');
		aa.href = '/fix-settings';
		aa.appendChild (icb);
		aa.appendChild (tt);
		sb.getElementsByTagName ('div') [0].appendChild (aa); // created by YT scripts
		let bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
		bb.setAttribute ('aria-label', 'fixes settings');
		let ic = document.createElement ('yt-icon');
		ic.className = 'style-scope ytd-topbar-menu-button-renderer';
		bb.appendChild (ic);
		let 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-2zM2 20v1l1 1h1l5-5v-2h-2zM13 15l2-2 8 8v1l-1 1h-1zM15 14l-1 1 7 7h1v-1zM9 11l2-2-2-2 1.5-3-3-3h-2l3 3-1.5 3-3 1.5-3-3v2l3 3 3-1.5zM9 10l-2-2 1-1 2 2z');
		let svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
		svgg.className.baseVal = 'style-scope yt-icon';
		svgg.appendChild (gpath);
		let 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
		}
	inject_func.push (createSettingsButton);
	inject_ints.push ({ call: createSettingsButton, params: [fix_version, {}] });
	// styles
	function AddStyles () {
		if (styles.length == 0)
			return;
		if (!document.head)
			return setTimeout (AddStyles, 1);
	    let style_element = document.createElement ('style');
	    style_element.type = 'text/css';
	    style_element.innerHTML = styles.join ('');
		document.head.appendChild (style_element);
		}
	AddStyles ();
	// injection
	function InjectInterval () {
		for (let i = ints.length; --i >= 0; ) {
			const Q = ints [i];
			try { Q.call.apply (this, Q.params); }
			catch (e) { }
			}
		}
	let ss = ['(function () {'];
	for (let i = inject_func.length; --i >= 0; )
		ss.push (inject_func [i].toString ());
	ss.push ('let ints = [');
	for (let i = inject_ints.length; --i >= 0; ) {
		const Q = inject_ints [i];
		ss.push ('{ call: ' + Q.call.name + ', params: ' + JSON.stringify (Q.params || []) + '},');
		}
	ss.push ('];');
	ss.push (InjectInterval.toString ());
	ss.push ('setInterval (InjectInterval, 1000);');
	ss.push ('console.log ("Fixes injected");');
	ss.push ('}) ();');
	ss = ss.join ('\n');
	let sse = document.createElement ('script');
	sse.setAttribute ('id', 'ytfixscript');
	sse.appendChild (document.createTextNode (ss));
	(document.body || document.head || document.documentElement).appendChild (sse);
	console.log ('Fixes loaded');
	}) ();