Youtube polymer engine fixes

Some fixes for Youtube polymer engine

目前为 2021-01-04 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube polymer engine fixes
  3. // @description Some fixes for Youtube polymer engine
  4. // @namespace bo.gd.an[at]rambler.ru
  5. // @version 2.3.0
  6. // @match https://www.youtube.com/*
  7. // @compatible firefox 56
  8. // @author Bogudan
  9. // @grant none
  10. // @run-at document-start
  11. // ==/UserScript==
  12.  
  13. //debug helper:
  14. //window.YTFixes = {};
  15. (function() {
  16. 'use strict';
  17. let fix_version = '2.3.0'; // as close to header as possible: in hopes to not forget
  18. if (window.YTEngine2) return; // in-development kill-switch
  19. if (document.location.pathname == '/error') // there is nothing to do on error page
  20. return;
  21. // test local storage availability (required for settings saving) and load settings
  22. let settings = {}, ls;
  23. try {
  24. function lsTest (st, v) {
  25. st.setItem ('__fix_test__', v);
  26. return st.getItem ('__fix_test__') == v;
  27. };
  28. let _s = window.localStorage;
  29. if (lsTest (_s, 'qwe') && lsTest (_s, 'rty')) { // do 2 times just in case LS stored value once, but does not let change it later
  30. ls = _s;
  31. settings = JSON.parse (ls.getItem ('__fix__settings__')) || {};
  32. }
  33. }
  34. catch (e) { }
  35. // delete old settings
  36. if ("default_player_640" in settings) {
  37. settings.default_player = settings.default_player_640 ? 3 : 0;
  38. delete settings.default_player_640;
  39. }
  40. if ("reduce_thumbnail" in settings) {
  41. settings.thumbnail_size = settings.reduce_thumbnail ? 2 : 0;
  42. delete settings.reduce_thumbnail;
  43. }
  44. // set default values
  45. settings.version = fix_version;
  46. if (!("inst_ver" in settings))
  47. settings.inst_ver = fix_version;
  48. if (!("align_player" in settings))
  49. settings.align_player = 0;
  50. if (!("default_player" in settings))
  51. settings.default_player = 0;
  52. if (!("hide_guide" in settings))
  53. settings.hide_guide = true;
  54. if (!("hide_yt_suggested_blocks" in settings))
  55. settings.hide_yt_suggested_blocks = true;
  56. if (!("logo_target" in settings))
  57. settings.logo_target = "";
  58. if (!("reduce_font" in settings))
  59. settings.reduce_font = true;
  60. if (!("theater_player" in settings))
  61. settings.theater_player = 0;
  62. if (!("thumbnail_size" in settings))
  63. settings.thumbnail_size = 2;
  64. if (!("thumbnail_size_m" in settings))
  65. settings.thumbnail_size_m = 720;
  66. if (!("unfix_header" in settings))
  67. settings.unfix_header = true;
  68. if (!("search_thumbnail" in settings))
  69. settings.search_thumbnail = 0;
  70. if (!("clear_search" in settings))
  71. settings.clear_search = false;
  72. if (!("channel_top" in settings))
  73. settings.channel_top = 0;
  74. if (!("try_load_more" in settings))
  75. settings.try_load_more = false;
  76. if (!("unbound_video_title" in settings))
  77. settings.unbound_video_title = false;
  78. if (!("video_quality" in settings))
  79. settings.video_quality = 0;
  80. console.log ('fix settings:', settings);
  81. // catch "settings" page
  82. if (document.location.pathname == '/fix-settings') {
  83. document.title = "YouTube Polymer Fixes: Settings";
  84. let back = document.createElement ('div');
  85. back.className = 'ytfixback';
  86. let plane = document.createElement ('div'), e1, e2;
  87. plane.className = 'ytfix';
  88. let style = document.createElement ('style');
  89. style.type = 'text/css';
  90. style.innerHTML = [
  91. '.ytfix{position:absolute;left:0;top:0;right:0;background:#eee;padding:3em}',
  92. '.ytfix_line{margin:1em}',
  93. '.ytfix_line span,.ytfix_line input,.ytfix_line select{margin-right:1em}',
  94. '.ytfix_field{padding:0.2em;border:1px solid #888}',
  95. '.ytfix_button{padding:0.4em;border:1px solid #888}',
  96. '.ytfix_hide{display:none}',
  97. '.ytfixback{position:absolute;left:0;top:0;right:0;height:100%;background:#eee}'
  98. ].join ('');
  99. plane.appendChild (style);
  100. function AddLine () {
  101. let q = document.createElement ('div');
  102. q.className = 'ytfix_line';
  103. for (let i = 0, L = arguments.length; i < L; ++i)
  104. q.appendChild (arguments [i]);
  105. plane.appendChild (q);
  106. return q;
  107. }
  108. e1 = document.createElement ('b');
  109. e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
  110. AddLine (e1);
  111. if (!ls) {
  112. e1 = document.createElement ('span');
  113. e1.style = 'color:red';
  114. e1.appendChild (document.createTextNode ('Cannot edit settings: no access to local storage.'));
  115. AddLine (e1);
  116. e1 = document.createElement ('span');
  117. e1.appendChild (document.createTextNode ('If you are using Firefox, allow cookies for this site.'));
  118. AddLine (e1);
  119. }
  120. else {
  121. let ess = {};
  122. function MakeDesc (desc) {
  123. let e = document.createElement ('span');
  124. e.appendChild (document.createTextNode (desc));
  125. return e;
  126. }
  127. function MakeBoolElement (nm) {
  128. let e = document.createElement ('input');
  129. e.type = 'checkbox';
  130. e.checked = settings [nm];
  131. ess [nm] = e;
  132. return e;
  133. }
  134. function MakeListElement (nm, opts) {
  135. let e = document.createElement ('select');
  136. e.className = 'ytfix_field';
  137. ess [nm] = e;
  138. for (let i = 0, L = opts.length; i < L; ++i) {
  139. let o = document.createElement ('option');
  140. o.appendChild (document.createTextNode (opts [i]));
  141. //if (i == val)
  142. // o.setAttribute ('selected', '');
  143. e.appendChild (o);
  144. }
  145. e.selectedIndex = settings [nm];
  146. return e;
  147. }
  148. function MakeTextElement (nm) {
  149. let e = document.createElement ('input');
  150. e.className = 'ytfix_field';
  151. e.value = settings [nm];
  152. ess [nm] = e;
  153. return e;
  154. }
  155. AddLine (MakeBoolElement ("hide_guide"), MakeDesc ('Hide "Guide" menu when page opens'));
  156. AddLine (MakeBoolElement ("reduce_font"), MakeDesc ("Reduce font size in video descriptions"));
  157. let tsm = MakeTextElement ("thumbnail_size_m");
  158. tsm.className = settings.thumbnail_size == 5 ? 'ytfix_field' : 'ytfix_hide';
  159. let tsi = MakeListElement ("thumbnail_size", ['default', '180px', '240px', '360px', '480px', 'manual']);
  160. tsi.addEventListener ('change', function () {
  161. ess.thumbnail_size_m.className = ess.thumbnail_size.selectedIndex == 5 ? 'ytfix_field' : 'ytfix_hide';
  162. });
  163. AddLine (MakeDesc ('Set thumbnails width for front page'), tsi, tsm);
  164. AddLine (MakeDesc ('Set thumbnails width for search page'), MakeListElement ("search_thumbnail", ['default', '240px', '360px']));
  165. AddLine (MakeDesc ("Set player size in default mode"), MakeListElement ("default_player", ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px']));
  166. AddLine (MakeDesc ("Set player size in theater mode"), MakeListElement ("theater_player", ['default', '256x144px', '426x240px', '640x360px', '853x480px', '1280x720px']));
  167. AddLine (MakeBoolElement ("hide_yt_suggested_blocks"), MakeDesc ('Hide suggestions blocks on main page (recommended playlists, latest posts, etc.)'));
  168. AddLine (MakeBoolElement ("clear_search"), MakeDesc ("Hide suggestions blocks in search (for you, people also watched, etc.)"));
  169. AddLine (MakeBoolElement ("unfix_header"), MakeDesc ("Unstick header bar from top of the screen"));
  170. AddLine (MakeDesc ("Align resized player into it's container (normal and theater modes)"), MakeListElement ("align_player", ['center', 'left', 'right']));
  171. AddLine (MakeDesc ("Modify channels' pages behaviour"), MakeListElement ('channel_top', ['default', 'hide banner with scrolling', 'hide banner on load']));
  172. AddLine (MakeBoolElement ('try_load_more'), MakeDesc ('Add button to try loading more content on pages with dynamic content load'));
  173. AddLine (MakeBoolElement ('unbound_video_title'), MakeDesc ('Remove size limit for video titles'));
  174. AddLine (MakeDesc ("Change YT logo target to https://www.youtube.com/..."), MakeTextElement ("logo_target"));
  175. AddLine (MakeDesc ('Starting video quality'), MakeListElement ('video_quality', ['Auto (default)', '2160p (4K)', '1440p (HD)', '1080p (HD)', '720p', '480p', '360p', '240p', '144p']));
  176. e1 = document.createElement ('input');
  177. e1.type = 'button';
  178. e1.className = 'ytfix_button';
  179. e1.value = 'Save settings and return to YouTube';
  180. e1.addEventListener ('click', function () {
  181. settings.hide_guide = ess.hide_guide.checked;
  182. settings.reduce_font = ess.reduce_font.checked;
  183. settings.thumbnail_size = ess.thumbnail_size.selectedIndex;
  184. if (settings.thumbnail_size == 5) {
  185. let v = ess.thumbnail_size_m.value;
  186. if (!/^\d+$/.test (v)) {
  187. alert ('Error: invalid value for thumbnails size');
  188. return;
  189. }
  190. settings.thumbnail_size_m = parseInt (v);
  191. }
  192. settings.search_thumbnail = ess.search_thumbnail.selectedIndex;
  193. settings.default_player = ess.default_player.selectedIndex;
  194. settings.theater_player = ess.theater_player.selectedIndex;
  195. settings.hide_yt_suggested_blocks = ess.hide_yt_suggested_blocks.checked;
  196. settings.unfix_header = ess.unfix_header.checked;
  197. settings.align_player = ess.align_player.selectedIndex;
  198. settings.channel_top = ess.channel_top.selectedIndex;
  199. settings.logo_target = ess.logo_target.value;
  200. settings.clear_search = ess.clear_search.checked;
  201. settings.try_load_more = ess.try_load_more.checked;
  202. settings.unbound_video_title = ess.unbound_video_title.checked;
  203. settings.video_quality = ess.video_quality.selectedIndex;
  204. ls.setItem ('__fix__settings__', JSON.stringify (settings));
  205. alert ('Settings saved');
  206. history.back ();
  207. });
  208. e2 = document.createElement ('input');
  209. e2.type = 'button';
  210. e2.className = 'ytfix_button';
  211. e2.value = 'Return to YouTube without saving';
  212. e2.addEventListener ('click', function () {
  213. history.back ();
  214. });
  215. AddLine (e1, e2);
  216. }
  217. let int = setInterval (function () {
  218. if (!document.body)
  219. return;
  220. document.body.appendChild (back);
  221. document.body.appendChild (plane);
  222. clearInterval (int);
  223. }, 1);
  224. }
  225. // apply settings
  226. let styles = [], intervals = [];
  227. function addInterval (period, func, params) {
  228. if (!period)
  229. period = 1;
  230. intervals.push ({ cnt: period, period: period, call: func, params: params || [] });
  231. }
  232. if (settings.hide_guide)
  233. addInterval (1, function (info) {
  234. if (info.act == 0) { // observe location change
  235. let url = document.location.toString ();
  236. if (url != info.url)
  237. info.act = 1;
  238. }
  239. if (info.act == 1) { // wait for sorp page load completion
  240. let Q = document.getElementsByTagName ('yt-page-navigation-progress');
  241. if (!Q.length)
  242. return;
  243. if (Q [0].hasAttribute ('hidden'))
  244. info.act = 2;
  245. }
  246. if (info.act == 2) { // wait for button and press it if necessary
  247. let guide_button = document.getElementById ('guide-button');
  248. if (!guide_button)
  249. return;
  250. let tmp = guide_button.getElementsByTagName ('button');
  251. if (!tmp.length)
  252. return;
  253. tmp = tmp [0];
  254. if (!tmp.hasAttribute ('aria-pressed'))
  255. return;
  256. if (tmp.attributes ['aria-pressed'].value == 'true')
  257. guide_button.click ();
  258. else {
  259. info.url = document.location.toString ();
  260. info.act = 0;
  261. window.dispatchEvent (new Event ('resize'));
  262. }
  263. }
  264. }, [{ act: 2 }]);
  265. // old engine:
  266. //if (document.getElementById ('appbar-guide-button'))
  267. // document.documentElement.classList.remove ('show-guide');
  268. if (settings.reduce_font) {
  269. 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}');
  270. // fix: "not interested" placeholder bigger than original element by about 40%
  271. styles.push ('paper-button.style-blue-text{padding:0!important}');
  272. }
  273. if (settings.thumbnail_size)
  274. styles.push ('ytd-rich-item-renderer{width:' + [0, 180, 240, 360, 480, settings.thumbnail_size_m] [settings.thumbnail_size] + 'px!important}');
  275. if (settings.hide_yt_suggested_blocks)
  276. styles.push ('div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}');
  277. if (settings.unfix_header) {
  278. styles.push ('div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app,app-drawer#guide{position:absolute!important}');
  279. styles.push ('ytd-feed-filter-chip-bar-renderer{position:relative!important}');
  280. styles.push ('div#chips-wrapper{position:absolute!important;top:0!important}');
  281. }
  282. if (settings.search_thumbnail) {
  283. let sz = [0, 240, 360] [settings.search_thumbnail] + 'px!important';
  284. // min-width defaults to 240px, max-width defaults to 360px
  285. // sizes for: videos, playlists, channels, mixes
  286. 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 + '}');
  287. }
  288. if (settings.clear_search)
  289. 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}');
  290. styles.push ([
  291. '#player-theater-container{margin-left:auto!important;margin-right:auto!important}',
  292. '#player-container-outer{margin-left:0!important}',
  293. '#player-container-outer{margin-right:0!important}#player-theater-container{margin-left:auto!important}',
  294. ] [settings.align_player]);
  295. const sizes = [undefined, { w: 256, h: 244 }, { w: 427, h: 240 }, { w: 640, h: 360 }, { w: 854, h: 480 }, { w: 1280, h: 720 }];
  296. let size_norm = sizes [settings.default_player];
  297. if (size_norm)
  298. styles.push ('#player-container-outer{min-width:' + size_norm.w + 'px!important;max-width:' + size_norm.w + 'px!important}');
  299. let size_theater = sizes [settings.theater_player];
  300. if (size_theater)
  301. 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}');
  302. if (size_norm || size_theater)
  303. addInterval (1, function (sn, st) {
  304. let eq = document.getElementsByTagName ("ytd-watch-flexy");
  305. if (!eq.length)
  306. return;
  307. let s = eq [0].hasAttribute ('theater') ? st : sn;
  308. if (!s)
  309. return;
  310. let ep = document.getElementById ("movie_player");
  311. if (ep && ep.setInternalSize && ep.isFullscreen && ep.getPlayerSize && !ep.isFullscreen () && ep.getPlayerSize ().width != s [0])
  312. ep.setInternalSize (s [0], s [1]);
  313. }, [size_norm, size_theater]);
  314. if (settings.logo_target) {
  315. let X = settings.logo_target;
  316. if (X [0] != '/')
  317. X = '/' + X;
  318. X = document.location.origin + X;
  319. addInterval (1, function (url) {
  320. let l = document.querySelectorAll ('a#logo');
  321. for (let i = l.length; --i >= 0; ) {
  322. let Q = l [i];
  323. let D = Q.data;
  324. if (D && D.commandMetadata && Q.href != url) {
  325. Q.href = url;
  326. D.commandMetadata.webCommandMetadata.url = url;
  327. }
  328. }
  329. }, [X]);
  330. }
  331. if (settings.channel_top)
  332. 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}');
  333. if (settings.channel_top > 1) {
  334. styles.push ('div#contentContainer.style-scope.app-header-layout{padding-top:148px!important}');
  335. styles.push ('div#contentContainer.style-scope.app-header{height:148px!important}');
  336. styles.push ('div.banner-visible-area.style-scope.ytd-c4-tabbed-header-renderer{display:none!important}');
  337. }
  338. if (settings.try_load_more) {
  339. function TryLoadMore () {
  340. let l = document.querySelectorAll ('#show-more-button');
  341. let i = l.length;
  342. if (--i >= 0 && l [i].hasAttribute ('hidden')) {
  343. l [i].removeAttribute ('hidden');
  344. l [i].innerText = 'TRY LOAD MORE';
  345. }
  346. while (--i >= 0)
  347. l [i].parentNode.removeChild (l [i]);
  348. }
  349. addInterval (1, TryLoadMore, []);
  350. styles.push ('#show-more-button{color:var(--yt-spec-call-to-action);width:100%;text-align:center;border:1px solid;padding:1em;cursor:pointer}');
  351. }
  352. if (settings.unbound_video_title)
  353. styles.push ('#video-title{max-height:none!important}');
  354. if (settings.video_quality) {
  355. const qv = ['', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny'];
  356. function IsQualityAvailable (qq, q) {
  357. for (let i = qq.length; --i >= 0; )
  358. if (q == qq [i])
  359. return true;
  360. return false;
  361. }
  362. function UpdateVideoQuality (st) {
  363. let ep = document.getElementById ("movie_player");
  364. if (!ep || !ep.getPreferredQuality || !ep.getAvailableQualityLevels || !ep.setPlaybackQualityRange || !ep.getVideoData || ep.getPreferredQuality () != 'auto')
  365. return;
  366. let vid = ep.getVideoData ().video_id;
  367. if (st.fail == vid) // last time on this video we've issues
  368. return;
  369. let qq = ep.getAvailableQualityLevels ();
  370. if (!qq || !qq.length)
  371. return;
  372. let det = settings.video_quality;
  373. while (det < qv.length && !IsQualityAvailable (qq, qv [det]))
  374. ++det;
  375. if (det == qv.length) {
  376. console.log ('Unknown video qualities in list: ', qq);
  377. st.fail = vid;
  378. return;
  379. }
  380. ep.setPlaybackQualityRange (qv [det], qv [det]);
  381. };
  382. addInterval (1, UpdateVideoQuality, [{}]);
  383. }
  384. // "settings" button
  385. // can't store created button: Polymer overrides it's content on soft reload leaving tags in place
  386. // but can store element that Polymer does not know how to deal with and just drops
  387. let settingsButtonMark;
  388. function createSettingsButton () {
  389. if (settingsButtonMark && settingsButtonMark.parentNode)
  390. return;
  391. let toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
  392. let _1st = toolBar [0];
  393. if (!_1st)
  394. return;
  395. toolBar = _1st.parentNode;
  396. let sb = document.createElement ('ytd-topbar-menu-button-renderer');
  397. sb.className = 'style-scope ytd-masthead style-default';
  398. sb.setAttribute ('use-keyboard-focused', '');
  399. sb.setAttribute ('is-icon-button', '');
  400. sb.setAttribute ('has-no-text', '');
  401. toolBar.insertBefore (sb, toolBar.childNodes [0]);
  402. let mark = document.createElement ('fix-settings-mark');
  403. mark.style = 'display:none';
  404. toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
  405. let icb = document.createElement ('yt-icon-button');
  406. icb.id = 'button';
  407. icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
  408. let aa = document.createElement ('a');
  409. aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
  410. aa.setAttribute ('tabindex', '-1');
  411. aa.href = '/fix-settings';
  412. aa.appendChild (icb);
  413. sb.getElementsByTagName ('div') [0].appendChild (aa); // created by YT scripts
  414. let bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
  415. bb.setAttribute ('aria-label', 'fixes settings');
  416. let ic = document.createElement ('yt-icon');
  417. ic.className = 'style-scope ytd-topbar-menu-button-renderer';
  418. bb.appendChild (ic);
  419. let gpath = document.createElementNS ('http://www.w3.org/2000/svg', 'path');
  420. gpath.className.baseVal = 'style-scope yt-icon';
  421. 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');
  422. let svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
  423. svgg.className.baseVal = 'style-scope yt-icon';
  424. svgg.appendChild (gpath);
  425. let svg = document.createElementNS ('http://www.w3.org/2000/svg', 'svg');
  426. svg.className.baseVal = 'style-scope yt-icon';
  427. svg.setAttributeNS (null, 'viewBox', '0 0 24 24');
  428. svg.setAttributeNS (null, 'preserveAspectRatio', 'xMidYMid meet');
  429. svg.setAttribute ('focusable', 'false');
  430. svg.setAttribute ('style', 'pointer-events: none; display: block; width: 100%; height: 100%;');
  431. svg.appendChild (svgg);
  432. ic.appendChild (svg); // YT clears *ic
  433. settingsButtonMark = mark;
  434. }
  435. addInterval (1, createSettingsButton, []);
  436. // styles
  437. function AddStyles () {
  438. if (styles.length == 0)
  439. return;
  440. if (!document.head)
  441. return setTimeout (AddStyles, 1);
  442. let style_element = document.createElement ('style');
  443. style_element.type = 'text/css';
  444. style_element.innerHTML = styles.join ('');
  445. document.head.appendChild (style_element);
  446. }
  447. AddStyles ();
  448. // intervals
  449. setInterval (function () {
  450. for (let i = intervals.length; --i >= 0; ) {
  451. let Q = intervals [i];
  452. if (--Q.cnt > 0)
  453. continue;
  454. try { Q.call.apply (this, Q.params); } catch (e) { }
  455. Q.cnt = Q.period;
  456. }
  457. }, 1000);
  458. console.log ('Fixed loaded');
  459. }) ();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址