- // ==UserScript==
- // @name Youtube polymer engine fixes
- // @description Some fixes for Youtube polymer engine
- // @namespace bo.gd.an[at]rambler.ru
- // @version 2.3.0
- // @match https://www.youtube.com/*
- // @compatible firefox 56
- // @author Bogudan
- // @grant none
- // @run-at document-start
- // ==/UserScript==
-
- //debug helper:
- //window.YTFixes = {};
- (function() {
- 'use strict';
- let fix_version = '2.3.0'; // as close to header as possible: in hopes to not forget
- if (window.YTEngine2) return; // in-development kill-switch
- if (document.location.pathname == '/error') // there is nothing to do on error page
- return;
- // test local storage availability (required for settings saving) and load settings
- let settings = {}, ls;
- 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;
- settings = JSON.parse (ls.getItem ('__fix__settings__')) || {};
- }
- }
- catch (e) { }
- // 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;
- 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 (!ls) {
- e1 = document.createElement ('span');
- e1.style = 'color:red';
- e1.appendChild (document.createTextNode ('Cannot edit settings: no access to local 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 (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;
- ls.setItem ('__fix__settings__', JSON.stringify (settings));
- 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);
- }
- let int = setInterval (function () {
- if (!document.body)
- return;
- document.body.appendChild (back);
- document.body.appendChild (plane);
- clearInterval (int);
- }, 1);
- }
- // apply settings
- let styles = [], intervals = [];
- function addInterval (period, func, params) {
- if (!period)
- period = 1;
- intervals.push ({ cnt: period, period: period, call: func, params: params || [] });
- }
- if (settings.hide_guide)
- addInterval (1, function (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'));
- }
- }
- }, [{ act: 2 }]);
- // old engine:
- //if (document.getElementById ('appbar-guide-button'))
- // document.documentElement.classList.remove ('show-guide');
- 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}');
- }
- 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}');
- 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)
- addInterval (1, function (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]);
- }, [size_norm, size_theater]);
- if (settings.logo_target) {
- let X = settings.logo_target;
- if (X [0] != '/')
- X = '/' + X;
- X = document.location.origin + X;
- addInterval (1, function (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;
- }
- }
- }, [X]);
- }
- 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]);
- }
- addInterval (1, 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) {
- const qv = ['', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
- function IsQualityAvailable (qq, q) {
- for (let i = qq.length; --i >= 0; )
- if (q == qq [i])
- return true;
- return false;
- }
- function UpdateVideoQuality (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;
- let det = settings.video_quality;
- while (det < qv.length && !IsQualityAvailable (qq, qv [det]))
- ++det;
- if (det == qv.length) {
- console.log ('Unknown video qualities in list: ', qq);
- st.fail = vid;
- return;
- }
- ep.setPlaybackQualityRange (qv [det], qv [det]);
- };
- addInterval (1, UpdateVideoQuality, [{}]);
- }
- // "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
- let settingsButtonMark;
- function createSettingsButton () {
- if (settingsButtonMark && settingsButtonMark.parentNode)
- return;
- let toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
- 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
- let icb = document.createElement ('yt-icon-button');
- icb.id = 'button';
- icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
- 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);
- 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-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');
- 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
- settingsButtonMark = mark;
- }
- addInterval (1, createSettingsButton, []);
- // 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 ();
- // intervals
- setInterval (function () {
- for (let i = intervals.length; --i >= 0; ) {
- let Q = intervals [i];
- if (--Q.cnt > 0)
- continue;
- try { Q.call.apply (this, Q.params); } catch (e) { }
- Q.cnt = Q.period;
- }
- }, 1000);
- console.log ('Fixed loaded');
- }) ();