Youtube polymer engine fixes

Some fixes for Youtube polymer engine

目前為 2021-07-19 提交的版本,檢視 最新版本

  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.9.3
  6. // @match https://www.youtube.com/*
  7. // @compatible firefox 56
  8. // @author Bogudan
  9. // @grant GM_info
  10. // @grant GM.info
  11. // @grant GM_getValue
  12. // @grant GM.getValue
  13. // @grant GM_setValue
  14. // @grant GM.setValue
  15. // @noframes
  16. // @run-at document-start
  17. // @license For personal use only
  18. // @require https://gf.qytechs.cn/scripts/427473-object-setdefault/code/object_setDefault.js?version=937466
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. 'use strict';
  23. if (document.location.pathname == '/error') // нам нечего делать на страницах с ошибками
  24. return;
  25. // test local storage availability and load settings from there first
  26. let settings, ls, saver;
  27. try {
  28. function lsTest (st, v) {
  29. st.setItem ('__fix_test__', v);
  30. return st.getItem ('__fix_test__') == v;
  31. };
  32. const _s = window.localStorage;
  33. if (lsTest (_s, 'qwe') && lsTest (_s, 'rty')) { // do 2 times just in case LS stored value once, but does not let change it later
  34. ls = _s;
  35. ls.removeItem ('__fix_test__');
  36. settings = JSON.parse (ls.getItem ('__fix__settings__'));
  37. }
  38. }
  39. catch (e) { }
  40. // select storage: GM_*/GM.* or local storage (in case userscript manager does not grant us GMs)
  41. if (typeof (GM_getValue) !== 'undefined' && typeof (GM_setValue) !== 'undefined' && GM_getValue && GM_setValue) {
  42. saver = function () {
  43. GM_setValue ('settings', settings);
  44. }
  45. if (!settings)
  46. settings = GM_getValue ('settings', {});
  47. else {
  48. saver ();
  49. ls.removeItem ('__fix__settings__');
  50. }
  51. settings.storage = 'GM_*Value';
  52. }
  53. else if (typeof (GM) !== 'undefined' && GM && GM.getValue && GM.setValue) {
  54. saver = function () {
  55. (async () => await GM.setValue ('settings', settings)) ();
  56. };
  57. if (!settings)
  58. settings = (async () => await GM.getValue ('settings', {})) ();
  59. else {
  60. saver ();
  61. ls.removeItem ('__fix__settings__');
  62. }
  63. settings.storage = 'GM.*Value';
  64. }
  65. else if (ls) {
  66. if (!settings)
  67. settings = {};
  68. saver = function () {
  69. ls.setItem ('__fix__settings__', JSON.stringify (settings));
  70. };
  71. settings.storage = 'window.localStorage.*Item';
  72. }
  73. else
  74. settings = {};
  75. // delete old settings
  76. if ("default_player_640" in settings) { // удалено в 0.5
  77. settings.default_player = settings.default_player_640 ? 3 : 0;
  78. delete settings.default_player_640;
  79. }
  80. if ("reduce_thumbnail" in settings) { // удалено в 0.6.0
  81. settings.thumbnail_size = settings.reduce_thumbnail ? 2 : 0;
  82. delete settings.reduce_thumbnail;
  83. }
  84. if ("reduce_font" in settings) { // удалено в 2.5.8: размеры текста уменьшились на стороне YT
  85. settings.fix_removed_placeholder = settings.reduce_font;
  86. delete settings.reduce_font;
  87. }
  88. if ("wide_description" in settings) { // удалено в 2.9.1
  89. settings.description_width = settings.wide_description ? 1 : 0;
  90. delete settings.wide_description;
  91. }
  92. // set default values
  93. const gminfo = typeof (GM_info) !== 'undefined' && GM_info || typeof (GM) !== 'undefined' && GM && GM.info;
  94. const fix_version = gminfo && gminfo.script && gminfo.script.version;
  95. if (fix_version) {
  96. settings.version = fix_version;
  97. settings.setDefault ("inst_ver", fix_version);
  98. }
  99. settings.setDefault ("align_player", 0);
  100. settings.setDefault ("default_player", 0);
  101. settings.setDefault ("hide_guide", true);
  102. settings.setDefault ("hide_yt_suggested_blocks", true);
  103. settings.setDefault ("logo_target", "");
  104. settings.setDefault ("fix_removed_placeholder", true);
  105. settings.setDefault ("theater_player", 0);
  106. settings.setDefault ("thumbnail_size", 2);
  107. settings.setDefault ("thumbnail_size_m", 720);
  108. settings.setDefault ("unfix_header", true);
  109. settings.setDefault ("search_thumbnail", 0);
  110. settings.setDefault ("clear_search", false);
  111. settings.setDefault ("channel_top", 0);
  112. settings.setDefault ("try_load_more", false);
  113. settings.setDefault ("unbound_video_title", false);
  114. settings.setDefault ("video_quality", 0);
  115. settings.setDefault ("no_resume_time", false);
  116. settings.setDefault ("remove_yt_redirect", false);
  117. settings.setDefault ("exact_view_count", false);
  118. settings.setDefault ("resume_bar_handling", settings.no_resume_time ? 1 : 0);
  119. settings.setDefault ("watched_grayscale", 0);
  120. settings.setDefault ("watched_blur", 0);
  121. settings.setDefault ("disable_player_click_overlay", false);
  122. settings.setDefault ("description_width", 0);
  123. settings.setDefault ("simpler_fullscreen", false);
  124. console.log ('fix settings:', settings);
  125. // catch "settings" page
  126. if (document.location.pathname == '/fix-settings') {
  127. document.title = "YouTube Polymer Fixes: Settings";
  128. const back = document.createElement ('div');
  129. back.className = 'ytfixback';
  130. const plane = document.createElement ('div');
  131. plane.className = 'ytfix';
  132. const style = document.createElement ('style');
  133. style.type = 'text/css';
  134. style.innerHTML = `
  135. .ytfix{position:absolute;left:0;top:0;right:0;background:#eee;padding:3em}
  136. .ytfix_line{margin:1em}
  137. .ytfix_line span,.ytfix_line input,.ytfix_line select{margin-right:1em}
  138. .ytfix_field{padding:0.2em;border:1px solid #888}
  139. .ytfix_button{padding:0.4em;border:1px solid #888}
  140. .ytfix_line fieldset {border:1px solid #ccc;padding:0 0.5em}
  141. .ytfix_line legend {padding:0 0.5em}
  142. .ytfix_line img {margin:0 1em}
  143. .ytfix_hide{display:none}
  144. .ytfixback{position:absolute;left:0;top:0;right:0;height:100%;background:#eee}
  145. .ytfix_slide_base {position:relative;margin-right:1em;display:inline-block;height:1em}
  146. .ytfix_slide_bar {position:absolute;left:0;right:0;top:0.3em;bottom:0.3em;background:#ddd;border:1px solid #ccc}
  147. .ytfix_slide_stroke {position:absolute;width:1px;top:0.1em;bottom:0.1em;background:#ccc}
  148. .ytfix_slide_arrow {position:absolute;width:9px;bottom:0;top:0;background:#ddd;border:1px solid #aaa;border-radius:0.5em}
  149. .ytfix_tabs {position:relative}
  150. .ytfix_tabs > div {display:none;border:1px solid #ccc}
  151. .ytfix_tabs > input {display:none}
  152. .ytfix_tabs > label {display:inline-block;background:#ddd;border:1px solid #ccc;padding:0.5em 1em;position:relative;top:1px}
  153. .ytfix_tabs > label ~ label {border-left:none}
  154. .ytfix_tabs > input:checked + label {background-color:#eee;border-bottom:1px solid #eee}
  155. `;
  156. plane.appendChild (style);
  157. function AddLine (plane) {
  158. const q = document.createElement ('div');
  159. q.className = 'ytfix_line';
  160. for (let i = 1, L = arguments.length; i < L; ++i)
  161. q.appendChild (arguments [i]);
  162. plane.appendChild (q);
  163. return q;
  164. }
  165. let e1, e2;
  166. e1 = document.createElement ('b');
  167. e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
  168. AddLine (plane, e1);
  169. if (fix_version) {
  170. e1 = document.createElement ('b');
  171. e1.appendChild (document.createTextNode (`Version: ${fix_version}`));
  172. AddLine (plane, e1);
  173. }
  174. if (!saver) {
  175. e1 = document.createElement ('span');
  176. e1.style = 'color:red';
  177. e1.appendChild (document.createTextNode ('Cannot edit settings: no access to any storage.'));
  178. AddLine (plane, e1);
  179. e1 = document.createElement ('span');
  180. e1.appendChild (document.createTextNode ('If you are using Firefox, allow cookies for this site.'));
  181. AddLine (plane, e1);
  182. }
  183. else {
  184. const ess = {};
  185. function MakeDesc (desc, extra) {
  186. const e = document.createElement ('span');
  187. e.appendChild (document.createTextNode (desc));
  188. if (extra) {
  189. if (extra.style)
  190. e.style = style;
  191. if (extra.note) {
  192. const n = document.createElement ('sup');
  193. n.appendChild (document.createTextNode (extra.note));
  194. e.appendChild (n);
  195. }
  196. }
  197. return e;
  198. }
  199. function MakeNote (num, desc, extra) {
  200. const e = document.createElement ('span');
  201. if (num) {
  202. const n = document.createElement ('sup');
  203. n.appendChild (document.createTextNode (num));
  204. e.appendChild (n);
  205. }
  206. e.appendChild (document.createTextNode (desc));
  207. if (extra && extra.style)
  208. e.style = 'font-size:0.75em;' + extra.style;
  209. else
  210. e.style = 'font-size:0.75em';
  211. return e;
  212. }
  213. function MakeBoolElement (nm) {
  214. const e = document.createElement ('input');
  215. e.type = 'checkbox';
  216. e.checked = settings [nm];
  217. ess [nm] = e;
  218. return e;
  219. }
  220. function MakeListElement (nm, opts) {
  221. const e = document.createElement ('select');
  222. e.className = 'ytfix_field';
  223. ess [nm] = e;
  224. for (let i = 0, L = opts.length; i < L; ++i) {
  225. const o = document.createElement ('option');
  226. o.appendChild (document.createTextNode (opts [i]));
  227. e.appendChild (o);
  228. }
  229. e.selectedIndex = settings [nm];
  230. return e;
  231. }
  232. function MakeTextElement (nm) {
  233. const e = document.createElement ('input');
  234. e.className = 'ytfix_field';
  235. e.value = settings [nm];
  236. ess [nm] = e;
  237. return e;
  238. }
  239. function MakeSlider (nm, width, snap, steps) {
  240. let desc = { value : -1, mouse : false };
  241. const e = document.createElement ('div');
  242. e.className = 'ytfix_slide_base';
  243. e.style.width = `${width*snap*steps+1}px`;
  244. const b = document.createElement ('div');
  245. b.className = 'ytfix_slide_bar';
  246. e.appendChild (b);
  247. for (let x = width * snap * steps; x >= 0; x -= width * snap) {
  248. const s = document.createElement ('div');
  249. s.className = 'ytfix_slide_stroke';
  250. s.style.left = `${x}px`;
  251. e.appendChild (s);
  252. }
  253. const a = document.createElement ('div');
  254. a.className = 'ytfix_slide_arrow';
  255. e.appendChild (a);
  256. const i = document.createElement ('input');
  257. i.className = 'ytfix_field';
  258. i.type = 'number';
  259. i.style.width = `${(snap*steps).toString().length+2}em`;
  260. i.min = 0;
  261. i.max = snap * steps;
  262. i.step = 1;
  263. function UpdateValue (newvalue) {
  264. if (newvalue < 0)
  265. newvalue = 0;
  266. else if (newvalue > snap * steps)
  267. newvalue = snap * steps;
  268. if (newvalue == desc.value)
  269. return;
  270. desc.value = newvalue;
  271. a.style.left = `${desc.value*width-5}px`;
  272. i.value = desc.value;
  273. if (desc.callback)
  274. desc.callback (desc);
  275. }
  276. UpdateValue (settings [nm]);
  277. e.addEventListener ('mousedown', function (ev) {
  278. if (ev.buttons != 1)
  279. return;
  280. desc.mouse = ev.target === a;
  281. if (desc.mouse)
  282. return;
  283. let sliderRect = a.getBoundingClientRect ();
  284. if (ev.clientX <= sliderRect.left)
  285. UpdateValue (desc.value - snap);
  286. else if (ev.clientX > sliderRect.right)
  287. UpdateValue (desc.value + snap);
  288. });
  289. e.addEventListener ('mousemove', function (ev) {
  290. if (ev.buttons != 1 || !desc.mouse)
  291. return;
  292. let mx = ev.clientX - e.getBoundingClientRect ().left;
  293. mx += (width * snap) >> 1;
  294. mx -= mx % (width * snap);
  295. UpdateValue (mx / width);
  296. });
  297. i.addEventListener ('input', function () {
  298. if (/^\d+$/.test (i.value))
  299. UpdateValue (parseInt (i.value));
  300. });
  301. desc.base = e;
  302. desc.input = i;
  303. ess [nm] = desc;
  304. return desc;
  305. }
  306. function MakeButton (text, click) {
  307. const e = document.createElement ('input');
  308. e.type = 'button';
  309. e.className = 'ytfix_button';
  310. e.value = text;
  311. e.addEventListener ('click', click);
  312. return e;
  313. }
  314. const tabs_data = [];
  315. function MakeTab (name, text, checked) {
  316. const inp = document.createElement ('input');
  317. inp.type = 'radio';
  318. inp.id = name;
  319. inp.name = 'tabs';
  320. if (checked)
  321. inp.setAttribute ('checked', '');
  322. const lbl = document.createElement ('label');
  323. lbl.setAttribute ('for', name);
  324. lbl.appendChild (document.createTextNode (text));
  325. const cont = document.createElement ('div');
  326. cont.id = name + '_cont';
  327. style.innerHTML += `.ytfix_tabs > input#${name}:checked ~ div#${name}_cont {display:block}`;
  328. tabs_data.push ({ inp: inp, lbl: lbl, cont: cont });
  329. return cont;
  330. }
  331. const tab_gen = MakeTab ('tab_gen', 'General', true);
  332. const tab_front = MakeTab ('tab_front', 'Front page', false);
  333. const tab_search = MakeTab ('tab_search', 'Search', false);
  334. const tab_video = MakeTab ('tab_video', 'Video', false);
  335. const tab_channel = MakeTab ('tab_channel', 'Channel', false);
  336. const tabs_data_2 = [plane];
  337. tabs_data.forEach ((x) => tabs_data_2.push (x.inp, x.lbl));
  338. const tabbf = document.createElement ('div');
  339. tabbf.style = 'display:block;width:1px;height:2px;border-width:0 0 0 1px;position:absolute';
  340. tabs_data_2.push (tabbf);
  341. tabs_data.forEach ((x) => tabs_data_2.push (x.cont));
  342. const tabs = AddLine.apply (this, tabs_data_2);
  343. tabs.className += ' ytfix_tabs';
  344.  
  345. AddLine (tab_gen, MakeBoolElement ("hide_guide"), MakeDesc ('Hide "Guide" menu when page opens'));
  346. AddLine (tab_video, MakeBoolElement ("fix_removed_placeholder"), MakeDesc ('Make size of "Video removed" placeholder about the same as removed video description'));
  347. const tsm = MakeTextElement ("thumbnail_size_m");
  348. tsm.className = settings.thumbnail_size == 5 ? 'ytfix_field' : 'ytfix_hide';
  349. const tsi = MakeListElement ("thumbnail_size", ['default', '180px', '240px', '360px', '480px', 'manual']);
  350. tsi.addEventListener ('change', function () {
  351. ess.thumbnail_size_m.className = ess.thumbnail_size.selectedIndex == 5 ? 'ytfix_field' : 'ytfix_hide';
  352. });
  353. AddLine (tab_front, MakeDesc ('Set thumbnails width'), tsi, tsm);
  354. AddLine (tab_search, MakeDesc ('Set thumbnails width'), MakeListElement ("search_thumbnail", ['default', '240px', '360px']));
  355. AddLine (tab_video, MakeDesc ("Set player height in default mode"), MakeListElement ("default_player", ['default', '144px', '240px', '360px', '480px', '720px']));
  356. AddLine (tab_video, MakeDesc ("Set player height in theater mode"), MakeListElement ("theater_player", ['default', '144px', '240px', '360px', '480px', '720px']));
  357. AddLine (tab_front, MakeBoolElement ("hide_yt_suggested_blocks"), MakeDesc ('Hide suggestions blocks (recommended playlists, latest posts, etc.)'));
  358. AddLine (tab_search, MakeBoolElement ("clear_search"), MakeDesc ("Hide suggestions blocks (for you, people also watched, etc.)"));
  359. AddLine (tab_gen, MakeBoolElement ("unfix_header"), MakeDesc ("Unstick header bar from top of the screen"));
  360. AddLine (tab_video, MakeDesc ("Align resized player into it's container (normal and theater modes)"), MakeListElement ("align_player", ['center', 'left', 'right']));
  361. AddLine (tab_channel, MakeDesc ("Channel banner behaviour"), MakeListElement ('channel_top', ['default', 'hide banner with scrolling', 'hide banner entirely']));
  362. AddLine (tab_gen, MakeBoolElement ('try_load_more'), MakeDesc ('Add button to try loading more content on pages with dynamic content load'));
  363. AddLine (tab_gen, MakeBoolElement ('unbound_video_title'), MakeDesc ('Remove size limit for video titles'));
  364. AddLine (tab_gen, MakeDesc ("Change YT logo target to https://www.youtube.com/..."), MakeTextElement ("logo_target"));
  365. AddLine (tab_gen, MakeBoolElement ("remove_yt_redirect"), MakeDesc ('Remove YT tracking from links (/redirect?...)'));
  366. AddLine (tab_gen, MakeBoolElement ("no_resume_time"), MakeDesc ('Remove resume time from the video links (&t=...)'));
  367. AddLine (tab_gen, MakeDesc ('Video resume bar (red)'), MakeListElement ("resume_bar_handling", ['depending on resule time (default)', 'full width of thumbnail', 'hide']));
  368. const wwfs = document.createElement ('fieldset');
  369. const wwl = wwfs.appendChild (document.createElement ('legend'));
  370. wwl.appendChild (document.createTextNode ('Watched video thumbnails modification'));
  371. const wwt = wwfs.appendChild (document.createElement ('table')).appendChild (document.createElement ('tr'));
  372. const wwc1 = wwt.appendChild (document.createElement ('td'));
  373. const wwgs = MakeSlider ('watched_grayscale', 2, 10, 10);
  374. AddLine (wwc1, MakeDesc ('Grayscale, %'), wwgs.base, wwgs.input);
  375. const wwb = MakeSlider ('watched_blur', 50, 1, 4);
  376. AddLine (wwc1, MakeDesc ('Blur, px'), wwb.base, wwb.input);
  377. AddLine (wwc1, MakeNote (0, 'Options require user to be logged into YT account'));
  378. AddLine (wwc1, MakeNote (0, 'Sample image taken from https://unsplash.com/photos/n6TWNDfyPwk'));
  379. const wwc2 = wwt.appendChild (document.createElement ('td'));
  380. wwc2.style.textAlign = 'center';
  381. wwc2.appendChild (document.createTextNode ('Example'));
  382. wwc2.appendChild (document.createElement ('br'));
  383. wwc2.appendChild (document.createElement ('img')).src = 'https://picsum.photos/id/197/267/178';
  384. const wwc3 = wwt.appendChild (document.createElement ('td'));
  385. wwc3.style.textAlign = 'center';
  386. wwc3.appendChild (document.createTextNode ('Modified example'));
  387. wwc3.appendChild (document.createElement ('br'));
  388. const wwc3i = wwc3.appendChild (document.createElement ('img'));
  389. wwc3i.src = 'https://picsum.photos/id/197/267/178';
  390. function UpdateFilters () {
  391. wwc3i.style.filter = `grayscale(${wwgs.value}%)blur(${wwb.value}px)`;
  392. }
  393. UpdateFilters ();
  394. wwgs.callback = UpdateFilters;
  395. wwb.callback = UpdateFilters;
  396. AddLine (tab_gen, wwfs);
  397. AddLine (tab_video, MakeDesc ('Starting video quality'), MakeListElement ('video_quality', ['Auto (default)', '2160p (4K)', '1440p (HD)', '1080p (HD)', '720p', '480p', '360p', '240p', '144p']));
  398. AddLine (tab_gen, MakeBoolElement ("exact_view_count"), MakeDesc ('Show exact view counts in video descriptions'));
  399. AddLine (tab_video, MakeBoolElement ("disable_player_click_overlay"), MakeDesc ('Remove rewinding overlay'));
  400. AddLine (tab_video, MakeDesc ("Video description width (including suggested videos column)"), MakeListElement ("description_width", ['default', 'stretch', '1200px', '1280px', '1360px', '1440px', '1520px', '1600px', '1680px', '1760px', '1840px', '1920px']));
  401. AddLine (
  402. tab_video,
  403. MakeBoolElement ("simpler_fullscreen"),
  404. MakeDesc ("Simplify fullscreen (no video description, comments, etc.)"),
  405. MakeNote (0, "Option does not work with 'f' shortcut", { style: 'display:block;padding-left:2.5em' }),
  406. MakeNote (0, "Use Esc to exit fullscreen mode.", { style: 'display:block;padding-left:2.5em' })
  407. );
  408. e1 = MakeButton ('Save settings and return to YouTube', function () {
  409. settings.hide_guide = ess.hide_guide.checked;
  410. settings.fix_removed_placeholder = ess.fix_removed_placeholder.checked;
  411. settings.thumbnail_size = ess.thumbnail_size.selectedIndex;
  412. if (settings.thumbnail_size == 5) {
  413. const v = ess.thumbnail_size_m.value;
  414. if (!/^\d+$/.test (v)) {
  415. alert ('Error: invalid value for thumbnails size');
  416. return;
  417. }
  418. settings.thumbnail_size_m = parseInt (v);
  419. }
  420. settings.search_thumbnail = ess.search_thumbnail.selectedIndex;
  421. settings.default_player = ess.default_player.selectedIndex;
  422. settings.theater_player = ess.theater_player.selectedIndex;
  423. settings.hide_yt_suggested_blocks = ess.hide_yt_suggested_blocks.checked;
  424. settings.unfix_header = ess.unfix_header.checked;
  425. settings.align_player = ess.align_player.selectedIndex;
  426. settings.channel_top = ess.channel_top.selectedIndex;
  427. settings.logo_target = ess.logo_target.value;
  428. settings.clear_search = ess.clear_search.checked;
  429. settings.try_load_more = ess.try_load_more.checked;
  430. settings.unbound_video_title = ess.unbound_video_title.checked;
  431. settings.video_quality = ess.video_quality.selectedIndex;
  432. settings.no_resume_time = ess.no_resume_time.checked;
  433. settings.remove_yt_redirect = ess.remove_yt_redirect.checked;
  434. settings.exact_view_count = ess.exact_view_count.checked;
  435. settings.resume_bar_handling = ess.resume_bar_handling.selectedIndex;
  436. settings.watched_grayscale = ess.watched_grayscale.value;
  437. settings.watched_blur = ess.watched_blur.value;
  438. settings.disable_player_click_overlay = ess.disable_player_click_overlay.checked;
  439. settings.description_width = ess.description_width.selectedIndex;
  440. settings.simpler_fullscreen = ess.simpler_fullscreen.checked;
  441. saver ();
  442. alert ('Settings saved');
  443. history.back ();
  444. });
  445. e2 = MakeButton ('Return to YouTube without saving', function () {
  446. history.back ();
  447. });
  448. AddLine (plane, e1, e2);
  449. e1 = MakeButton ('Export settings', function () {
  450. const d = document.createElement ('a');
  451. d.style.display = 'none';
  452. d.setAttribute ('download', 'ytfix_settings.json');
  453. d.setAttribute ('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent (JSON.stringify (settings)));
  454. document.body.appendChild (d);
  455. d.click ();
  456. document.body.removeChild (d);
  457. });
  458. e2 = MakeButton ('Import settings', function () {
  459. const f = document.createElement ('input');
  460. f.type = 'file';
  461. f.style.display = 'none';
  462. f.addEventListener ('change', function () {
  463. if (f.files.length != 1)
  464. return;
  465. const rdr = new FileReader ();
  466. rdr.addEventListener ('load', function () {
  467. try {
  468. settings = JSON.parse (rdr.result);
  469. saver ();
  470. alert ('Settings imported');
  471. document.location.reload ();
  472. }
  473. catch (ex) {
  474. alert ('Error parsing settings\n' + ex);
  475. }
  476. });
  477. rdr.addEventListener ('error', () => alert ('Error loading file\n' + rdr.error));
  478. rdr.readAsText (f.files [0]);
  479. });
  480. document.body.appendChild (f);
  481. f.click ();
  482. document.body.removeChild (f);
  483. });
  484. AddLine (plane, e1, e2);
  485. }
  486. let int = setInterval (function () {
  487. if (!document.body)
  488. return;
  489. document.body.appendChild (back);
  490. document.body.appendChild (plane);
  491. clearInterval (int);
  492. }, 1);
  493. console.log ('Settings page created');
  494. return;
  495. }
  496. // apply settings
  497. let styles = '';
  498. const inject_func = [], inject_ints = [];
  499. if (settings.hide_guide) {
  500. function HideGuideTimer (info) {
  501. if (info.act == 0 && document.location.toString () != info.url) // обнаружение смены адреса
  502. info.act = 1;
  503. if (info.act == 1) { // wait for sorp page load completion
  504. const Q = document.getElementsByTagName ('yt-page-navigation-progress');
  505. if (!Q.length || !Q [0].hasAttribute ('hidden'))
  506. return;
  507. info.act = 2;
  508. }
  509. if (info.act == 2) { // wait for button and press it if necessary
  510. const guide_button = document.getElementById ('guide-button');
  511. if (!guide_button)
  512. return;
  513. let tmp = guide_button.getElementsByTagName ('button');
  514. if (!tmp.length)
  515. return;
  516. tmp = tmp [0];
  517. if (!tmp.hasAttribute ('aria-pressed'))
  518. return;
  519. if (tmp.attributes ['aria-pressed'].value == 'true')
  520. guide_button.click ();
  521. else {
  522. info.url = document.location.toString ();
  523. info.act = 0;
  524. window.dispatchEvent (new Event ('resize'));
  525. }
  526. }
  527. }
  528. inject_func.push (HideGuideTimer);
  529. inject_ints.push ({ call: HideGuideTimer, params: [{ act: 2 }] });
  530. }
  531. if (settings.fix_removed_placeholder)
  532. styles += 'paper-button.style-blue-text,tp-yt-paper-button.style-blue-text{padding:0!important}';
  533. if (settings.thumbnail_size)
  534. styles += 'ytd-rich-item-renderer{width:' + [0, 180, 240, 360, 480, settings.thumbnail_size_m] [settings.thumbnail_size] + 'px!important}';
  535. if (settings.hide_yt_suggested_blocks)
  536. styles += 'div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}';
  537. if (settings.unfix_header)
  538. styles += `
  539. div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app,app-drawer#guide{position:absolute!important}
  540. ytd-feed-filter-chip-bar-renderer{position:relative!important}
  541. div#chips-wrapper{position:absolute!important;top:0!important}
  542. `;
  543. if (settings.search_thumbnail) {
  544. const sz = [0, 240, 360] [settings.search_thumbnail] + 'px!important';
  545. // min-width defaults to 240px, max-width defaults to 360px
  546. // sizes for: videos, playlists, channels, mixes
  547. styles += `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}}`;
  548. }
  549. if (settings.clear_search)
  550. styles += '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}';
  551. styles += [
  552. '#player-theater-container{margin-left:auto!important;margin-right:auto!important}',
  553. '#player-container-outer{margin-left:0!important}',
  554. '#player-container-outer{margin-right:0!important}#player-theater-container{margin-left:auto!important}'
  555. ] [settings.align_player];
  556. const sizes = [0, 144, 240, 360, 480, 720];
  557. const size_norm = sizes [settings.default_player];
  558. if (size_norm)
  559. styles += `ytd-watch-flexy{--ytd-watch-flexy-min-player-height:${size_norm}px!important;--ytd-watch-flexy-max-player-height:${size_norm}px!important;--ytd-watch-flexy-max-player-width:var(--ytd-watch-flexy-min-player-width)!important}`;
  560. //#primary{min-width:calc(max(var(--ytd-watch-flexy-min-player-height)*16/9,var(--ytd-watch-flexy-min-player-width)))!important;max-width:calc(max(var(--ytd-watch-flexy-min-player-height)*16/9,var(--ytd-watch-flexy-min-player-width)))!important}
  561. const size_theater = sizes [settings.theater_player];
  562. if (size_theater)
  563. styles += `ytd-watch-flexy:not([fullscreen])[theater] #player-theater-container{min-width:calc(${size_theater}px*var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))!important;max-width:calc(${size_theater}px*var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))!important;min-height:${size_theater}px!important;max-height:${size_theater}px!important;height:${size_theater}px!important}`;
  564. if (size_norm || size_theater) {
  565. function SetPlayerSize (sn, st) {
  566. const eq = document.getElementsByTagName ("ytd-watch-flexy");
  567. if (!eq.length)
  568. return;
  569. const s = eq [0].hasAttribute ('theater') ? st : sn;
  570. if (!s)
  571. return;
  572. const ep = document.getElementById ("movie_player");
  573. if (ep && ep.setInternalSize && ep.isFullscreen && ep.getPlayerSize && !ep.isFullscreen () && ep.getPlayerSize ().height != s)
  574. ep.setInternalSize ();
  575. }
  576. inject_func.push (SetPlayerSize);
  577. inject_ints.push ({ call: SetPlayerSize, params: [size_norm, size_theater] });
  578. }
  579. if (settings.logo_target) {
  580. let url = settings.logo_target;
  581. if (url [0] != '/')
  582. url = '/' + url;
  583. url = document.location.origin + url;
  584. function SetLogoURL (url) {
  585. const l = document.querySelectorAll ('a#logo');
  586. for (let i = l.length; --i >= 0; ) {
  587. const Q = l [i];
  588. const D = Q.data;
  589. if (D && D.commandMetadata && Q.href != url) {
  590. Q.href = url;
  591. D.commandMetadata.webCommandMetadata.url = url;
  592. }
  593. }
  594. }
  595. inject_func.push (SetLogoURL);
  596. inject_ints.push ({ call: SetLogoURL, params: [url] });
  597. }
  598. if (settings.channel_top)
  599. styles += 'app-header#header.style-scope.ytd-c4-tabbed-header-renderer{transform:none!important;position:absolute;left:0px!important;top:0px;margin-top:0px}';
  600. if (settings.channel_top > 1)
  601. styles += `
  602. div#contentContainer.style-scope.app-header-layout{padding-top:148px!important}
  603. div#contentContainer.style-scope.app-header{height:148px!important}
  604. div.banner-visible-area.style-scope.ytd-c4-tabbed-header-renderer{display:none!important}
  605. `;
  606. if (settings.try_load_more) {
  607. function TryLoadMore () {
  608. const l = document.querySelectorAll ('#show-more-button');
  609. let i = l.length;
  610. if (--i >= 0 && l [i].hasAttribute ('hidden')) {
  611. l [i].removeAttribute ('hidden');
  612. l [i].innerText = 'TRY LOAD MORE';
  613. }
  614. while (--i >= 0)
  615. l [i].parentNode.removeChild (l [i]);
  616. }
  617. inject_func.push (TryLoadMore);
  618. inject_ints.push ({ call: TryLoadMore });
  619. styles += '#show-more-button{color:var(--yt-spec-call-to-action);width:100%;text-align:center;border:1px solid;padding:1em;cursor:pointer}';
  620. }
  621. if (settings.unbound_video_title)
  622. styles += '#video-title{max-height:none!important;-webkit-line-clamp:none!important}';
  623. if (settings.video_quality) {
  624. function TryQuality (quality, qq, ep) {
  625. return qq.includes (quality) && (ep.setPlaybackQualityRange (quality, quality) || true);
  626. }
  627. function UpdateVideoQuality (det, st) {
  628. const ep = document.getElementById ("movie_player");
  629. if (!ep || !ep.getPreferredQuality || !ep.getAvailableQualityLevels || !ep.setPlaybackQualityRange || !ep.getVideoData || ep.getPreferredQuality () != 'auto')
  630. return;
  631. const vid = ep.getVideoData ().video_id;
  632. if (st.fail == vid) // данное видео уже обработано
  633. return;
  634. const qq = ep.getAvailableQualityLevels ();
  635. if (!qq || !qq.length)
  636. return;
  637. switch (det) { // intentional no breaks here
  638. case 1: if (TryQuality ('hd2160', qq, ep)) return;
  639. case 2: if (TryQuality ('hd1440', qq, ep)) return;
  640. case 3: if (TryQuality ('hd1080', qq, ep)) return;
  641. case 4: if (TryQuality ('hd720', qq, ep)) return;
  642. case 5: if (TryQuality ('large', qq, ep)) return;
  643. case 6: if (TryQuality ('medium', qq, ep)) return;
  644. case 7: if (TryQuality ('small', qq, ep)) return;
  645. case 8: if (TryQuality ('tiny', qq, ep)) return;
  646. }
  647. console.log ('Unknown video qualities in list: ', qq);
  648. st.fail = vid;
  649. };
  650. inject_func.push (TryQuality);
  651. inject_func.push (UpdateVideoQuality);
  652. inject_ints.push ({ call: UpdateVideoQuality, params: [settings.video_quality, {}] });
  653. }
  654. if (settings.resume_bar_handling)
  655. styles += [
  656. '',
  657. 'div.ytd-thumbnail-overlay-resume-playback-renderer{width:100%!important}',
  658. 'ytd-thumbnail-overlay-resume-playback-renderer{display:none!important}'
  659. ] [settings.resume_bar_handling];
  660. let watched_filter = '';
  661. if (settings.watched_grayscale)
  662. watched_filter += `grayscale(${settings.watched_grayscale}%)`;
  663. if (settings.watched_blur)
  664. watched_filter += `blur(${settings.watched_blur}px)`;
  665. if (watched_filter)
  666. styles += `a[href*="/watch?"]:not([href^="/watch?"]).ytd-thumbnail img {filter:${watched_filter}}`;
  667. if (settings.no_resume_time || watched_filter) {
  668. function removeTimes (replace) {
  669. function removeTimesClearer (l) {
  670. while (l && l.tagName != 'A')
  671. l = l.parentNode;
  672. if (l)
  673. l.href = l.href.replace (/&t=\d+s?/, replace);
  674. }
  675. document.querySelectorAll ('a[href^="/watch?"] div.ytd-thumbnail-overlay-resume-playback-renderer').forEach (removeTimesClearer); // основное применение
  676. document.querySelectorAll ('a[href^="/watch?"][href*="&t="]').forEach (removeTimesClearer); // на случай прочих ссылок
  677. }
  678. inject_func.push (removeTimes);
  679. inject_ints.push ({ call: removeTimes, params: [settings.no_resume_time ? '' : '$1'] });
  680. }
  681. if (settings.remove_yt_redirect) {
  682. function removeRedirectClearer (l) {
  683. l.href = decodeURIComponent (l.href.replace (/^.*\?(.*&)q=([^&]+)(&.*)?$/, '$2'));
  684. l.data.urlEndpoint.url = l.href;
  685. }
  686. function removeRedirect () {
  687. document.querySelectorAll ('a[href^="https://www.youtube.com/redirect?"]').forEach (removeRedirectClearer);
  688. }
  689. inject_func.push (removeRedirectClearer);
  690. inject_func.push (removeRedirect);
  691. inject_ints.push ({ call: removeRedirect });
  692. }
  693. if (settings.exact_view_count) {
  694. function getCounterText (x) {
  695. try { return x.__data.data.viewCountText.simpleText; } catch (ex) { }
  696. try { return x.__data.data.content.videoRenderer.viewCountText.simpleText; } catch (ex) { }
  697. //console.log ('no viewers text found for:', x);
  698. return undefined;
  699. }
  700. function replaceCountersText (x) {
  701. const par = x.parentNode.__ytfix_parent;
  702. if (!par)
  703. return;
  704. const tgt = getCounterText (par);
  705. if (tgt && x.textContent != tgt)
  706. x.textContent = tgt;
  707. }
  708. function replaceCountersCallback (mm) {
  709. for (let i = mm.length; --i >= 0; ) {
  710. const m = mm [i];
  711. if (m.type == 'characterData')
  712. replaceCountersText (m.target);
  713. }
  714. }
  715. function replaceCounters (state) {
  716. if (!state.m) {
  717. state.m = new MutationObserver (replaceCountersCallback);
  718. state.opt = { subtree: true, characterData: true };
  719. }
  720. function replaceCountersEach (x) {
  721. x.setAttribute ('ytfix', '');
  722. const ee = x.querySelectorAll ('#metadata-line span');
  723. if (ee.length != 2)
  724. return;
  725. const e = ee [0];
  726. e.__ytfix_parent = x;
  727. replaceCountersText (e.firstChild);
  728. state.m.observe (e, state.opt);
  729. }
  730. document.querySelectorAll ('ytd-compact-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  731. document.querySelectorAll ('ytd-grid-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  732. document.querySelectorAll ('ytd-rich-item-renderer:not([ytfix])').forEach (replaceCountersEach);
  733. document.querySelectorAll ('ytd-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  734. }
  735. inject_func.push (getCounterText);
  736. inject_func.push (replaceCountersText);
  737. inject_func.push (replaceCountersCallback);
  738. inject_func.push (replaceCounters);
  739. inject_ints.push ({ call: replaceCounters, params: [{}] });
  740. }
  741. if (settings.disable_player_click_overlay)
  742. styles += 'div.ytp-doubletap-ui {display:none}';
  743. if (settings.description_width) {
  744. const w = [0, '100vw', '1200px', '1280px', '1360px', '1440px', '1520px', '1600px', '1680px', '1760px', '1840px', '1920px'] [settings.description_width];
  745. styles += `
  746. ytd-app:not([mini-guide-visible_]) ytd-page-manager {--ytf-width:calc(${w} - 20px);min-width:100%}
  747. ytd-app[mini-guide-visible_] ytd-page-manager {--ytf-width:calc(${w} - 92px);min-width:calc(100% - 92px)}
  748. ytd-watch-flexy[flexy][fullscreen] {min-width:100%!important}
  749. ytd-watch-flexy[flexy] #columns {--ytd-watch-flexy-min-player-width:calc(var(--ytf-width) - var(--ytd-watch-flexy-sidebar-width) - 3 * var(--ytd-margin-6x));min-width:var(--ytf-width)!important;max-width:var(--ytf-width)!important}
  750. `;
  751. }
  752. if (settings.simpler_fullscreen) {
  753. function UpdateFullscreen (state) {
  754. switch (state.s) {
  755. case 1:
  756. if (document [state.keys.check])
  757. document [state.keys.exit] ();
  758. break;
  759. case 2:
  760. let q = document.querySelectorAll ('ytd-player');
  761. if (q && q.length) {
  762. q = q [0];
  763. if (q !== document [state.keys.check])
  764. q [state.keys.enter] ();
  765. }
  766. break;
  767. }
  768. if (state.node)
  769. state.node.disabled = state.s == 2;
  770. }
  771. function CatchFullscreenButton (state) {
  772. if (state.s < 0)
  773. return;
  774. if (!state.s) {
  775. let dd = false;
  776. if (document.exitFullscreen)
  777. dd = document.fullscreenEnabled && { enter: 'requestFullscreen', exit: 'exitFullscreen', check: 'fullscreenElement', event: 'fullscreenchange', type: 'standart' };
  778. else if (document.mozCancelFullScreen)
  779. dd = document.mozFullScreenEnabled && { enter: 'mozRequestFullScreen', exit: 'mozCancelFullScreen', check: 'mozFullScreenElement', event: 'mozfullscreenchange', type: 'mozilla' };
  780. else if (document.webkitExitFullscreen)
  781. dd = document.webkitFullscreenEnabled && { enter: 'webkitRequestFullscreen', exit: 'webkitExitFullscreen', check: 'webkitFullscreenElement', event: 'webkitfullscreenchange', type: 'webkit' };
  782. else if (document.msExitFullscreen)
  783. dd = document.msFullscreenEnabled && { enter: 'msRequestFullscreen', exit: 'msExitFullscreen', check: 'msFullscreenElement', event: 'MSFullscreenChange', type: 'microsoft' };
  784. if (!dd) {
  785. state.s = -1;
  786. console.log ('unable to determine fullscreen API prefix');
  787. return;
  788. }
  789. console.log ('detected fullscreen API type:', dd.type);
  790. state.keys = dd;
  791. document.addEventListener ('mozfullscreenchange', function () {
  792. let e = document [dd.check];
  793. if (!e)
  794. state.s = 1;
  795. else if (e !== document.documentElement)
  796. return;
  797. UpdateFullscreen (state);
  798. });
  799. state.s = 1;
  800. }
  801. let p = document.querySelectorAll ('button.ytp-fullscreen-button');
  802. if (!p || !p.length)
  803. return;
  804. p = p [0];
  805. if (p === state.node)
  806. return;
  807. state.node = p;
  808. p.addEventListener ('click', function () {
  809. state.s = 2;
  810. UpdateFullscreen (state);
  811. });
  812. }
  813. inject_func.push (UpdateFullscreen);
  814. inject_func.push (CatchFullscreenButton);
  815. inject_ints.push ({ call: CatchFullscreenButton, params: [{ s: 0 }] });
  816. }
  817. // "settings" button
  818. // can't store created button: Polymer overrides it's content on soft reload leaving tags in place
  819. // but can store element that Polymer does not know how to deal with and just drops
  820. function createSettingsButton (fix_version, st) {
  821. if (st.mark && st.mark.parentNode)
  822. return;
  823. let toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
  824. if (!toolBar.length)
  825. return;
  826. toolBar = toolBar [0];
  827. if (!toolBar)
  828. return;
  829. toolBar = toolBar.parentNode;
  830. const sb = document.createElement ('ytd-topbar-menu-button-renderer'); // ytd-notification-topbar-button-renderer
  831. sb.className = 'style-scope ytd-masthead style-default'; // style-scope ytd-masthead notification-button-style-type-default
  832. sb.setAttribute ('use-keyboard-focused', '');
  833. sb.setAttribute ('is-icon-button', '');
  834. sb.setAttribute ('has-no-text', '');
  835. toolBar.insertBefore (sb, toolBar.childNodes [0]);
  836. // div[id=notification-count][class=style-scope ytd-notification-topbar-button-renderer][innerHTML=...]
  837. const mark = document.createElement ('fix-settings-mark');
  838. mark.style = 'display:none';
  839. toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
  840. st.mark = mark;
  841. const icb = document.createElement ('yt-icon-button');
  842. icb.id = 'button';
  843. icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
  844. const tt = document.createElement ('tp-yt-paper-tooltip');
  845. tt.className = 'style-scope ytd-topbar-menu-button-renderer';
  846. tt.setAttribute ('role', 'tooltip');
  847. tt.setAttribute ('tabindex', '-1');
  848. tt.style = 'right:auto;bottom:auto';
  849. tt.appendChild (document.createTextNode ('YT fixes ' + fix_version)); // YT wraps content into DIV element
  850. const aa = document.createElement ('a');
  851. aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
  852. aa.setAttribute ('tabindex', '-1');
  853. aa.href = '/fix-settings';
  854. aa.appendChild (icb);
  855. aa.appendChild (tt);
  856. sb.getElementsByTagName ('div') [0].appendChild (aa); // created by YT scripts
  857. const bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
  858. bb.setAttribute ('aria-label', 'fixes settings');
  859. const ic = document.createElement ('yt-icon');
  860. ic.className = 'style-scope ytd-topbar-menu-button-renderer';
  861. bb.appendChild (ic);
  862. const gpath = document.createElementNS ('http://www.w3.org/2000/svg', 'path');
  863. gpath.className.baseVal = 'style-scope yt-icon';
  864. 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');
  865. const svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
  866. svgg.className.baseVal = 'style-scope yt-icon';
  867. svgg.appendChild (gpath);
  868. const svg = document.createElementNS ('http://www.w3.org/2000/svg', 'svg');
  869. svg.className.baseVal = 'style-scope yt-icon';
  870. svg.setAttributeNS (null, 'viewBox', '0 0 24 24');
  871. svg.setAttributeNS (null, 'preserveAspectRatio', 'xMidYMid meet');
  872. svg.setAttribute ('focusable', 'false');
  873. svg.setAttribute ('style', 'pointer-events: none; display: block; width: 100%; height: 100%;');
  874. svg.appendChild (svgg);
  875. ic.appendChild (svg); // YT clears *ic
  876. }
  877. inject_func.push (createSettingsButton);
  878. inject_ints.push ({ call: createSettingsButton, params: [fix_version, {}] });
  879. // styles
  880. function AddStyles () {
  881. if (styles.length == 0)
  882. return;
  883. if (!document.head)
  884. return setTimeout (AddStyles, 1);
  885. const style_element = document.createElement ('style');
  886. style_element.setAttribute ('type', 'text/css');
  887. style_element.setAttribute ('id', 'ytfixstyle');
  888. style_element.innerHTML = styles;
  889. document.head.appendChild (style_element);
  890. }
  891. AddStyles ();
  892. // injection
  893. function InjectScripts () {
  894. if (inject_ints.length == 0)
  895. return;
  896. if (!document.head)
  897. return setTimeout (InjectScripts, 1);
  898. function InjectInterval () {
  899. for (let i = ints.length; --i >= 0; ) {
  900. const Q = ints [i];
  901. try { Q.call.apply (this, Q.params); }
  902. catch (e) { }
  903. }
  904. }
  905. let ss = '(function () {\n';
  906. for (let i = inject_func.length; --i >= 0; )
  907. ss += `${inject_func [i]}\n`;
  908. ss += 'let ints = [\n';
  909. for (let i = inject_ints.length; --i >= 0; ) {
  910. const Q = inject_ints [i];
  911. ss += `{ call: ${Q.call.name}, params: ${JSON.stringify (Q.params || [])} },\n`;
  912. }
  913. ss += `];\n${InjectInterval}\nsetInterval (InjectInterval, 1000);\nconsole.log ("Fixes injected");\n}) ();`;
  914. const sse = document.createElement ('script');
  915. sse.setAttribute ('id', 'ytfixscript');
  916. sse.appendChild (document.createTextNode (ss));
  917. document.head.appendChild (sse);
  918. }
  919. InjectScripts ();
  920. console.log ('Fixes loaded');
  921. }) ();

QingJ © 2025

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