Youtube polymer engine fixes

Some fixes for Youtube polymer engine

目前為 2023-06-11 提交的版本,檢視 最新版本

  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.24.0
  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. // ==/UserScript==
  19.  
  20. (async function () {
  21. 'use strict';
  22. if (window.top !== window.self)
  23. return;
  24. if (location.pathname == '/error') // нам нечего делать на страницах с ошибками
  25. return;
  26. function setDefault (obj, key, value) {
  27. if (!(key in obj))
  28. obj [key] = value;
  29. }
  30. // test local storage availability and load settings from there first
  31. let settings, ls;
  32. try {
  33. function lsTest (st, v) {
  34. st.setItem ('__fix_test__', v);
  35. return st.getItem ('__fix_test__') == v;
  36. };
  37. const _s = window.localStorage;
  38. if (lsTest (_s, 'qwe') && lsTest (_s, 'rty')) { // do 2 times just in case LS stored value once, but does not let change it later
  39. ls = _s;
  40. ls.removeItem ('__fix_test__');
  41. settings = JSON.parse (ls.getItem ('__fix__settings__'));
  42. }
  43. }
  44. catch (e) { }
  45. // select storage: GM_*/GM.* or local storage (in case userscript manager does not grant us GMs)
  46. let storage = {};
  47. if (typeof (GM_getValue) !== 'undefined' && typeof (GM_setValue) !== 'undefined' && GM_getValue && GM_setValue) {
  48. storage.load = GM_getValue;
  49. storage.save = GM_setValue;
  50. if (!settings)
  51. settings = await storage.load ('settings', {});
  52. else {
  53. storage.save ('settings', settings);
  54. ls.removeItem ('__fix__settings__');
  55. }
  56. settings.storage = 'GM_*Value';
  57. }
  58. else if (typeof (GM) !== 'undefined' && GM && GM.getValue && GM.setValue) {
  59. storage.load = GM.getValue;
  60. storage.save = GM.setValue;
  61. if (!settings)
  62. settings = await storage.load ('settings', {});
  63. else {
  64. storage.save ('settings', settings);
  65. ls.removeItem ('__fix__settings__');
  66. }
  67. settings.storage = 'GM.*Value';
  68. }
  69. else if (ls) {
  70. storage.load = function (id, def) {
  71. const vv = ls.getItem (`__fix__{id}__`);
  72. return vv === null ? def : JSON.parse (vv);
  73. };
  74. storage.save = function (id, value) { ls.setItem (`__fix__{id}__`, JSON.stringify (value)); };
  75. if (!settings)
  76. settings = {};
  77. settings.storage = 'window.localStorage.*Item';
  78. }
  79. else
  80. settings = {};
  81. // delete old settings
  82. if ("default_player_640" in settings) { // удалено в 0.5
  83. settings.default_player = settings.default_player_640 ? 3 : 0;
  84. delete settings.default_player_640;
  85. }
  86. if ("reduce_thumbnail" in settings) { // удалено в 0.6.0
  87. settings.thumbnail_size = settings.reduce_thumbnail ? 2 : 0;
  88. delete settings.reduce_thumbnail;
  89. }
  90. if ("reduce_font" in settings) { // удалено в 2.5.8: размеры текста уменьшились на стороне YT
  91. settings.fix_removed_placeholder = settings.reduce_font;
  92. delete settings.reduce_font;
  93. }
  94. if ("wide_description" in settings) { // удалено в 2.9.1
  95. settings.description_width = settings.wide_description ? 1 : 0;
  96. delete settings.wide_description;
  97. }
  98. if ("restore_dislikes" in settings) // удалено в 2.14.3 -- данные больше не предоставляются
  99. delete settings.restore_dislikes;
  100. if ("exact_view_count" in settings) { // удалено в 2.16.0
  101. settings.view_count_mod = settings.exact_view_count ? 2 : 1;
  102. delete settings.exact_view_count;
  103. }
  104. if ("short_to_full" in settings) { // удалено в 2.20.0
  105. settings.short_to_full2 = settings.short_to_full ? 2 : 0;
  106. delete settings.short_to_full;
  107. }
  108. // set default values
  109. const gminfo = typeof (GM_info) !== 'undefined' && GM_info || typeof (GM) !== 'undefined' && GM && GM.info;
  110. const fix_version = gminfo && gminfo.script && gminfo.script.version;
  111. let load_version = settings.version;
  112. if (fix_version) {
  113. settings.version = fix_version;
  114. setDefault (settings, "inst_ver", fix_version);
  115. }
  116. setDefault (settings, "align_player", 0);
  117. setDefault (settings, "default_player", 0);
  118. setDefault (settings, "hide_guide", true);
  119. setDefault (settings, "hide_yt_suggested_blocks", true);
  120. setDefault (settings, "logo_target", "");
  121. setDefault (settings, "fix_removed_placeholder", true);
  122. setDefault (settings, "theater_player", 0);
  123. setDefault (settings, "thumbnail_size", 2);
  124. setDefault (settings, "thumbnail_size_m", 720);
  125. setDefault (settings, "unfix_header", true);
  126. setDefault (settings, "search_thumbnail", 0);
  127. setDefault (settings, "clear_search", false);
  128. setDefault (settings, "channel_top", 0);
  129. setDefault (settings, "try_load_more", false);
  130. setDefault (settings, "unbound_video_title", false);
  131. setDefault (settings, "video_quality", 0);
  132. setDefault (settings, "no_resume_time", false);
  133. setDefault (settings, "remove_yt_redirect", false);
  134. setDefault (settings, "resume_bar_handling", settings.no_resume_time ? 1 : 0);
  135. setDefault (settings, "watched_grayscale", 0);
  136. setDefault (settings, "watched_blur", 0);
  137. setDefault (settings, "disable_player_click_overlay", false);
  138. setDefault (settings, "description_width", 0);
  139. setDefault (settings, "simpler_fullscreen", false);
  140. setDefault (settings, 'clear_link_pp', false);
  141. setDefault (settings, 'exact_likes', false);
  142. setDefault (settings, 'disable_video_preview', false);
  143. setDefault (settings, 'hide_engagement_panel_transcript', false);
  144. setDefault (settings, 'hide_engagement_panel_structured_description', false);
  145. setDefault (settings, 'update_alert', 0);
  146. setDefault (settings, 'view_count_mod', 1);
  147. setDefault (settings, 'hide_metadata', false);
  148. setDefault (settings, 'remove_rounded_corners_1', false);
  149. setDefault (settings, 'thumbnail_size_channel', settings.thumbnail_size);
  150. setDefault (settings, 'thumbnail_size_channel_m', settings.thumbnail_size_m);
  151. setDefault (settings, 'short_to_full2', 0);
  152. setDefault (settings, 'channel_default', 0);
  153. setDefault (settings, 'clear_sign', false);
  154. setDefault (settings, 'main_align', 0);
  155. setDefault (settings, 'video_shelves', false);
  156. setDefault (settings, 'subscriptions_align', 0);
  157. console.log ('fix settings:', settings);
  158. // catch "settings" page
  159. if (location.pathname == '/fix-settings') {
  160. window.addEventListener ('DOMContentLoaded', () => { document.title = "YouTube Polymer Fixes: Settings"; });
  161. const back = document.createElement ('div');
  162. back.className = 'ytfixback';
  163. const plane = document.createElement ('div');
  164. plane.className = 'ytfix';
  165. const style = document.createElement ('style');
  166. style.type = 'text/css';
  167. style.innerHTML = `
  168. .ytfix{position:absolute;left:0;top:0;right:0;background:#eee;padding:3em}
  169. .ytfix_line{margin:1em}
  170. .ytfix_line span,.ytfix_line input,.ytfix_line select{margin-right:1em}
  171. .ytfix_field{padding:0.2em;border:1px solid #888}
  172. .ytfix_button{padding:0.4em;border:1px solid #888}
  173. .ytfix_line fieldset {border:1px solid #ccc;padding:0 0.5em}
  174. .ytfix_line legend {padding:0 0.5em}
  175. .ytfix_line img {margin:0 1em}
  176. .ytfix_hide{display:none}
  177. .ytfixback{position:absolute;left:0;top:0;right:0;height:100%;background:#eee}
  178. .ytfix_slide_base {position:relative;margin-right:1em;display:inline-block;height:1em}
  179. .ytfix_slide_bar {position:absolute;left:0;right:0;top:0.3em;bottom:0.3em;background:#ddd;border:1px solid #ccc}
  180. .ytfix_slide_stroke {position:absolute;width:1px;top:0.1em;bottom:0.1em;background:#ccc}
  181. .ytfix_slide_arrow {position:absolute;width:9px;bottom:0;top:0;background:#ddd;border:1px solid #aaa;border-radius:0.5em}
  182. .ytfix_tabs {position:relative}
  183. .ytfix_tabs > div {display:none;border:1px solid #ccc}
  184. .ytfix_tabs > input {display:none}
  185. .ytfix_tabs > label {display:inline-block;background:#ddd;border:1px solid #ccc;padding:0.5em 1em;position:relative;top:1px}
  186. .ytfix_tabs > label ~ label {border-left:none}
  187. .ytfix_tabs > input:checked + label {background-color:#eee;border-bottom:1px solid #eee}
  188. `;
  189. plane.appendChild (style);
  190. function AddLine (plane) {
  191. const q = document.createElement ('div');
  192. q.className = 'ytfix_line';
  193. for (let i = 1, L = arguments.length; i < L; ++i)
  194. q.appendChild (arguments [i]);
  195. plane.appendChild (q);
  196. return q;
  197. }
  198. let e1, e2;
  199. e1 = document.createElement ('b');
  200. e1.appendChild (document.createTextNode ('YouTube Polymer Fixes: Settings'));
  201. AddLine (plane, e1);
  202. if (fix_version) {
  203. e1 = document.createElement ('b');
  204. e1.appendChild (document.createTextNode (`Version: ${fix_version}`));
  205. AddLine (plane, e1);
  206. }
  207. if (!storage.save) {
  208. e1 = document.createElement ('span');
  209. e1.style = 'color:red';
  210. e1.appendChild (document.createTextNode ('Cannot edit settings: no access to any storage.'));
  211. AddLine (plane, e1);
  212. e1 = document.createElement ('span');
  213. e1.appendChild (document.createTextNode ('Please, allow cookies for this site.'));
  214. AddLine (plane, e1);
  215. }
  216. else {
  217. const ess = {};
  218. function MakeDesc (desc, extra) {
  219. const e = document.createElement ('span');
  220. e.appendChild (document.createTextNode (desc));
  221. if (extra) {
  222. if (extra.style)
  223. e.style = style;
  224. if (extra.note) {
  225. const n = document.createElement ('sup');
  226. n.appendChild (document.createTextNode (extra.note));
  227. e.appendChild (n);
  228. }
  229. }
  230. return e;
  231. }
  232. function MakeNote (num, desc, extra) {
  233. const e = document.createElement ('span');
  234. if (num) {
  235. const n = document.createElement ('sup');
  236. n.appendChild (document.createTextNode (num));
  237. e.appendChild (n);
  238. }
  239. e.appendChild (document.createTextNode (desc));
  240. if (extra && extra.style)
  241. e.style = 'font-size:0.75em;' + extra.style;
  242. else
  243. e.style = 'font-size:0.75em';
  244. return e;
  245. }
  246. function MakeBoolElement (nm) {
  247. const e = document.createElement ('input');
  248. e.type = 'checkbox';
  249. e.checked = settings [nm];
  250. ess [nm] = e;
  251. return e;
  252. }
  253. function MakeListElement (nm, opts) {
  254. const e = document.createElement ('select');
  255. e.className = 'ytfix_field';
  256. ess [nm] = e;
  257. for (let i = 0, L = opts.length; i < L; ++i) {
  258. const o = document.createElement ('option');
  259. o.appendChild (document.createTextNode (opts [i]));
  260. e.appendChild (o);
  261. }
  262. e.selectedIndex = settings [nm];
  263. return e;
  264. }
  265. function MakeTextElement (nm) {
  266. const e = document.createElement ('input');
  267. e.className = 'ytfix_field';
  268. e.value = settings [nm];
  269. ess [nm] = e;
  270. return e;
  271. }
  272. function MakeSlider (nm, width, snap, steps) {
  273. let desc = { value : -1, mouse : false };
  274. const e = document.createElement ('div');
  275. e.className = 'ytfix_slide_base';
  276. e.style.width = `${width*snap*steps+1}px`;
  277. const b = document.createElement ('div');
  278. b.className = 'ytfix_slide_bar';
  279. e.appendChild (b);
  280. for (let x = width * snap * steps; x >= 0; x -= width * snap) {
  281. const s = document.createElement ('div');
  282. s.className = 'ytfix_slide_stroke';
  283. s.style.left = `${x}px`;
  284. e.appendChild (s);
  285. }
  286. const a = document.createElement ('div');
  287. a.className = 'ytfix_slide_arrow';
  288. e.appendChild (a);
  289. const i = document.createElement ('input');
  290. i.className = 'ytfix_field';
  291. i.type = 'number';
  292. i.style.width = `${(snap*steps).toString().length+2}em`;
  293. i.min = 0;
  294. i.max = snap * steps;
  295. i.step = 1;
  296. function UpdateValue (newvalue) {
  297. if (newvalue < 0)
  298. newvalue = 0;
  299. else if (newvalue > snap * steps)
  300. newvalue = snap * steps;
  301. if (newvalue == desc.value)
  302. return;
  303. desc.value = newvalue;
  304. a.style.left = `${desc.value*width-5}px`;
  305. i.value = desc.value;
  306. if (desc.callback)
  307. desc.callback (desc);
  308. }
  309. UpdateValue (settings [nm]);
  310. e.addEventListener ('mousedown', function (ev) {
  311. if (ev.buttons != 1)
  312. return;
  313. desc.mouse = ev.target === a;
  314. if (desc.mouse)
  315. return;
  316. let sliderRect = a.getBoundingClientRect ();
  317. if (ev.clientX <= sliderRect.left)
  318. UpdateValue (desc.value - snap);
  319. else if (ev.clientX > sliderRect.right)
  320. UpdateValue (desc.value + snap);
  321. });
  322. e.addEventListener ('mousemove', function (ev) {
  323. if (ev.buttons != 1 || !desc.mouse)
  324. return;
  325. let mx = ev.clientX - e.getBoundingClientRect ().left;
  326. mx += (width * snap) >> 1;
  327. mx -= mx % (width * snap);
  328. UpdateValue (mx / width);
  329. });
  330. i.addEventListener ('input', function () {
  331. if (/^\d+$/.test (i.value))
  332. UpdateValue (parseInt (i.value));
  333. });
  334. desc.base = e;
  335. desc.input = i;
  336. ess [nm] = desc;
  337. return desc;
  338. }
  339. function MakeButton (text, click) {
  340. const e = document.createElement ('input');
  341. e.type = 'button';
  342. e.className = 'ytfix_button';
  343. e.value = text;
  344. e.addEventListener ('click', click);
  345. return e;
  346. }
  347. const tabs_data = [];
  348. function MakeTab (name, text, checked) {
  349. const inp = document.createElement ('input');
  350. inp.type = 'radio';
  351. inp.id = name;
  352. inp.name = 'tabs';
  353. if (checked)
  354. inp.setAttribute ('checked', '');
  355. const lbl = document.createElement ('label');
  356. lbl.setAttribute ('for', name);
  357. lbl.appendChild (document.createTextNode (text));
  358. const cont = document.createElement ('div');
  359. cont.id = name + '_cont';
  360. style.innerHTML += `.ytfix_tabs > input#${name}:checked ~ div#${name}_cont {display:block}`;
  361. tabs_data.push ({ inp: inp, lbl: lbl, cont: cont });
  362. return cont;
  363. }
  364. const tab_gen = MakeTab ('tab_gen', 'General', true);
  365. const tab_front = MakeTab ('tab_front', 'Front page', false);
  366. const tab_search = MakeTab ('tab_search', 'Search', false);
  367. const tab_video = MakeTab ('tab_video', 'Video', false);
  368. const tab_channel = MakeTab ('tab_channel', 'Channel', false);
  369. const tab_subscriptions = MakeTab ('tab_subscriptions', 'Subscriptions', false);
  370. const tab_script = MakeTab ('tab_script', 'Script', false);
  371. const tabs_data_2 = [plane];
  372. tabs_data.forEach ((x) => tabs_data_2.push (x.inp, x.lbl));
  373. const tabbf = document.createElement ('div');
  374. tabbf.style = 'display:block;width:1px;height:2px;border-width:0 0 0 1px;position:absolute';
  375. tabs_data_2.push (tabbf);
  376. tabs_data.forEach ((x) => tabs_data_2.push (x.cont));
  377. const tabs = AddLine.apply (this, tabs_data_2);
  378. tabs.className += ' ytfix_tabs';
  379.  
  380. AddLine (tab_gen, MakeBoolElement ("hide_guide"), MakeDesc ('Hide "Guide" menu when page opens'));
  381. AddLine (tab_video, MakeBoolElement ("fix_removed_placeholder"), MakeDesc ('Make size of "Video removed" placeholder about the same as removed video description'));
  382. const tsm = MakeTextElement ("thumbnail_size_m");
  383. tsm.className = settings.thumbnail_size == 5 ? 'ytfix_field' : 'ytfix_hide';
  384. const tsi = MakeListElement ("thumbnail_size", ['default', '180px', '240px', '360px', '480px', 'manual']);
  385. tsi.addEventListener ('change', function () {
  386. ess.thumbnail_size_m.className = ess.thumbnail_size.selectedIndex == 5 ? 'ytfix_field' : 'ytfix_hide';
  387. });
  388. AddLine (tab_front, MakeDesc ('Set thumbnails width'), tsi, tsm);
  389. AddLine (tab_search, MakeDesc ('Set thumbnails width'), MakeListElement ("search_thumbnail", ['default', '240px', '360px']));
  390. AddLine (tab_video, MakeDesc ("Set player height in default mode"), MakeListElement ("default_player", ['default', '144px', '240px', '360px', '480px', '720px']));
  391. AddLine (tab_video, MakeDesc ("Set player height in theater mode"), MakeListElement ("theater_player", ['default', '144px', '240px', '360px', '480px', '720px']));
  392. AddLine (tab_front, MakeBoolElement ("hide_yt_suggested_blocks"), MakeDesc ('Hide suggestions blocks (recommended playlists, latest posts, etc.)'));
  393. AddLine (tab_search, MakeBoolElement ("clear_search"), MakeDesc ("Hide suggestions blocks (for you, people also watched, etc.)"));
  394. AddLine (tab_gen, MakeBoolElement ("unfix_header"), MakeDesc ("Unstick header bar from top of the screen"));
  395. AddLine (tab_video, MakeDesc ("Align resized player into it's container (normal and theater modes)"), MakeListElement ("align_player", ['center', 'left', 'right']));
  396. AddLine (tab_channel, MakeDesc ("Channel banner behaviour"), MakeListElement ('channel_top', ['default', 'hide banner with scrolling', 'hide banner entirely']));
  397. AddLine (tab_gen, MakeBoolElement ('try_load_more'), MakeDesc ('Add button to try loading more content on pages with dynamic content load'));
  398. AddLine (tab_gen, MakeBoolElement ('unbound_video_title'), MakeDesc ('Remove size limit for video titles'));
  399. AddLine (tab_gen, MakeDesc ("Change YT logo target to https://www.youtube.com/..."), MakeTextElement ("logo_target"));
  400. AddLine (tab_gen, MakeBoolElement ("remove_yt_redirect"), MakeDesc ('Remove YT tracking from links (/redirect?...)'));
  401. AddLine (tab_gen, MakeBoolElement ("no_resume_time"), MakeDesc ('Remove resume time from the video links (&t=...)'));
  402. AddLine (tab_gen, MakeDesc ('Video resume bar (red)'), MakeListElement ("resume_bar_handling", ['depending on resume time (default)', 'full width of thumbnail', 'hide']));
  403. const wwfs = document.createElement ('fieldset');
  404. const wwl = wwfs.appendChild (document.createElement ('legend'));
  405. wwl.appendChild (document.createTextNode ('Watched video thumbnails modification'));
  406. const wwt = wwfs.appendChild (document.createElement ('table')).appendChild (document.createElement ('tr'));
  407. const wwc1 = wwt.appendChild (document.createElement ('td'));
  408. const wwgs = MakeSlider ('watched_grayscale', 2, 10, 10);
  409. AddLine (wwc1, MakeDesc ('Grayscale, %'), wwgs.base, wwgs.input);
  410. const wwb = MakeSlider ('watched_blur', 50, 1, 4);
  411. AddLine (wwc1, MakeDesc ('Blur, px'), wwb.base, wwb.input);
  412. AddLine (wwc1, MakeNote (0, 'Options require user to be logged into YT account'));
  413. AddLine (wwc1, MakeNote (0, 'Sample image taken from https://unsplash.com/photos/n6TWNDfyPwk'));
  414. const wwc2 = wwt.appendChild (document.createElement ('td'));
  415. wwc2.style.textAlign = 'center';
  416. wwc2.appendChild (document.createTextNode ('Example'));
  417. wwc2.appendChild (document.createElement ('br'));
  418. wwc2.appendChild (document.createElement ('img')).src = 'https://picsum.photos/id/197/267/178';
  419. const wwc3 = wwt.appendChild (document.createElement ('td'));
  420. wwc3.style.textAlign = 'center';
  421. wwc3.appendChild (document.createTextNode ('Modified example'));
  422. wwc3.appendChild (document.createElement ('br'));
  423. const wwc3i = wwc3.appendChild (document.createElement ('img'));
  424. wwc3i.src = 'https://picsum.photos/id/197/267/178';
  425. function UpdateFilters () {
  426. wwc3i.style.filter = `grayscale(${wwgs.value}%)blur(${wwb.value}px)`;
  427. }
  428. UpdateFilters ();
  429. wwgs.callback = UpdateFilters;
  430. wwb.callback = UpdateFilters;
  431. AddLine (tab_gen, wwfs);
  432. AddLine (tab_video, MakeDesc ('Starting video quality'), MakeListElement ('video_quality', ['Auto (default)', '2160p (4K)', '1440p (HD)', '1080p (HD)', '720p', '480p', '360p', '240p', '144p']),
  433. MakeNote (0, 'Settings other than "default" may break quality autoswitching if you use "Enhancer for YouTube" extention.', { style: 'margin-top:0.4em;display:block' })
  434. );
  435. AddLine (tab_gen, MakeDesc ('View count display modifier'), MakeListElement ('view_count_mod', ['Short everywhere', 'Exact on video page, short otherwise (default)', 'Exact everywhere']));
  436. AddLine (tab_video, MakeBoolElement ("disable_player_click_overlay"), MakeDesc ('Remove rewinding overlay'));
  437. 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']));
  438. AddLine (tab_video, MakeBoolElement ("simpler_fullscreen"), MakeDesc ("Simplify fullscreen (no video description, comments, etc.)"));
  439. AddLine (tab_gen, MakeBoolElement ("clear_link_pp"), MakeDesc ('Remove &pp= from links'));
  440. AddLine (tab_video, MakeBoolElement ('exact_likes'), MakeDesc ('Show exact likes count'));
  441. AddLine (tab_gen, MakeBoolElement ('disable_video_preview'), MakeDesc ('Disable video on-hover previews'));
  442. AddLine (tab_video, MakeBoolElement ('hide_engagement_panel_transcript'), MakeDesc ("Disable 'Transcript' side panel"));
  443. AddLine (tab_video, MakeBoolElement ('hide_engagement_panel_structured_description'), MakeDesc ("Disable 'Desciption' side panel"));
  444. AddLine (tab_video, MakeDesc ('Convert "shorts" video format to normal format'), MakeListElement ("short_to_full2", ['disabled', 'with page reload', 'without page reload']));
  445. AddLine (tab_video, MakeBoolElement ('hide_metadata'), MakeDesc ('Hide YT "metadata" (links to games, movies, etc.)'));
  446. AddLine (tab_script, MakeDesc ('Alert about script version changes'), MakeListElement ("update_alert", ['never', 'only major', 'except bugfixes', 'all']));
  447. AddLine (tab_script, MakeNote (0, "Version usually consist of 3 numbers (major version, minor version and release) and it's changes usually follow these rules:"));
  448. AddLine (tab_script, MakeNote (0, '* major version change indicates some incompatabilities with pervious versions;'));
  449. AddLine (tab_script, MakeNote (0, '* minor version change indicates some new functionality;'));
  450. AddLine (tab_script, MakeNote (0, '* release change indicate bugfixes or other internal improvements.'));
  451. AddLine (tab_gen, MakeBoolElement ('remove_rounded_corners_1'), MakeDesc ('Remove rounded corners on thumbnails and in video description'));
  452. const tscm = MakeTextElement ("thumbnail_size_channel_m");
  453. tscm.className = settings.thumbnail_size_channel == 5 ? 'ytfix_field' : 'ytfix_hide';
  454. const tsci = MakeListElement ("thumbnail_size_channel", ['default', '180px', '240px', '360px', '480px', 'manual']);
  455. tsci.addEventListener ('change', function () {
  456. ess.thumbnail_size_channel_m.className = ess.thumbnail_size_channel.selectedIndex == 5 ? 'ytfix_field' : 'ytfix_hide';
  457. });
  458. AddLine (tab_channel, MakeDesc ('Set thumbnails width'), tsci, tscm);
  459. AddLine (tab_channel, MakeDesc ("Switch from channel's home page to..."), MakeListElement ('channel_default', ['home (default)', 'videos', 'shorts', 'live', 'playlists', 'community', 'channeld', 'about']));
  460. AddLine (tab_gen, MakeBoolElement ('clear_sign'), MakeDesc ('Remove "$1" from visited video links'));
  461. AddLine (tab_front, MakeDesc ('Align main page content'), MakeListElement ("main_align", ['default', 'left', 'center', 'right']));
  462. AddLine (tab_video, MakeBoolElement ('video_shelves'), MakeDesc ('Remove shelves'));
  463. AddLine (tab_subscriptions, MakeDesc ('Align subscriptions page content'), MakeListElement ("subscriptions_align", ['default', 'left', 'center', 'right']));
  464. e1 = MakeButton ('Save settings and return to YouTube', function () {
  465. settings.hide_guide = ess.hide_guide.checked;
  466. settings.fix_removed_placeholder = ess.fix_removed_placeholder.checked;
  467. settings.thumbnail_size = ess.thumbnail_size.selectedIndex;
  468. if (settings.thumbnail_size == 5) {
  469. const v = ess.thumbnail_size_m.value;
  470. if (!/^\d+$/.test (v)) {
  471. alert ('Error: invalid value for thumbnails size');
  472. return;
  473. }
  474. settings.thumbnail_size_m = parseInt (v);
  475. }
  476. settings.search_thumbnail = ess.search_thumbnail.selectedIndex;
  477. settings.default_player = ess.default_player.selectedIndex;
  478. settings.theater_player = ess.theater_player.selectedIndex;
  479. settings.hide_yt_suggested_blocks = ess.hide_yt_suggested_blocks.checked;
  480. settings.unfix_header = ess.unfix_header.checked;
  481. settings.align_player = ess.align_player.selectedIndex;
  482. settings.channel_top = ess.channel_top.selectedIndex;
  483. settings.logo_target = ess.logo_target.value;
  484. settings.clear_search = ess.clear_search.checked;
  485. settings.try_load_more = ess.try_load_more.checked;
  486. settings.unbound_video_title = ess.unbound_video_title.checked;
  487. settings.video_quality = ess.video_quality.selectedIndex;
  488. settings.no_resume_time = ess.no_resume_time.checked;
  489. settings.remove_yt_redirect = ess.remove_yt_redirect.checked;
  490. settings.view_count_mod = ess.view_count_mod.selectedIndex;
  491. settings.resume_bar_handling = ess.resume_bar_handling.selectedIndex;
  492. settings.watched_grayscale = ess.watched_grayscale.value;
  493. settings.watched_blur = ess.watched_blur.value;
  494. settings.disable_player_click_overlay = ess.disable_player_click_overlay.checked;
  495. settings.description_width = ess.description_width.selectedIndex;
  496. settings.simpler_fullscreen = ess.simpler_fullscreen.checked;
  497. settings.clear_link_pp = ess.clear_link_pp.checked;
  498. settings.exact_likes = ess.exact_likes.checked;
  499. settings.disable_video_preview = ess.disable_video_preview.checked;
  500. settings.hide_engagement_panel_transcript = ess.hide_engagement_panel_transcript.checked;
  501. settings.hide_engagement_panel_structured_description = ess.hide_engagement_panel_structured_description.checked;
  502. settings.short_to_full2 = ess.short_to_full2.selectedIndex;
  503. settings.update_alert = ess.update_alert.selectedIndex;
  504. settings.hide_metadata = ess.hide_metadata.checked;
  505. settings.remove_rounded_corners_1 = ess.remove_rounded_corners_1.checked;
  506. settings.thumbnail_size_channel = ess.thumbnail_size_channel.selectedIndex;
  507. settings.channel_default = ess.channel_default.selectedIndex;
  508. settings.clear_sign = ess.clear_sign.checked;
  509. settings.main_align = ess.main_align.selectedIndex;
  510. settings.video_shelves = ess.video_shelves.checked;
  511. settings.subscriptions_align = ess.subscriptions_align.selectedIndex;
  512. if (settings.thumbnail_size_channel == 5) {
  513. const v = ess.thumbnail_size_channel_m.value;
  514. if (!/^\d+$/.test (v)) {
  515. alert ('Error: invalid value for thumbnails size');
  516. return;
  517. }
  518. settings.thumbnail_size_channel_m = parseInt (v);
  519. }
  520. storage.save ('settings', settings);
  521. alert ('Settings saved');
  522. history.back ();
  523. });
  524. e2 = MakeButton ('Return to YouTube without saving', function () {
  525. history.back ();
  526. });
  527. AddLine (plane, e1, e2);
  528. e1 = MakeButton ('Export settings', function () {
  529. const d = document.createElement ('a');
  530. d.style.display = 'none';
  531. d.setAttribute ('download', 'ytfix_settings.json');
  532. d.setAttribute ('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent (JSON.stringify (settings)));
  533. document.body.appendChild (d);
  534. d.click ();
  535. document.body.removeChild (d);
  536. });
  537. e2 = MakeButton ('Import settings', function () {
  538. const f = document.createElement ('input');
  539. f.type = 'file';
  540. f.style.display = 'none';
  541. f.addEventListener ('change', function () {
  542. if (f.files.length != 1)
  543. return;
  544. const rdr = new FileReader ();
  545. rdr.addEventListener ('load', function () {
  546. try {
  547. settings = JSON.parse (rdr.result);
  548. storage.save ('settings', settings);
  549. alert ('Settings imported');
  550. location.reload ();
  551. }
  552. catch (ex) {
  553. alert ('Error parsing settings\n' + ex);
  554. }
  555. });
  556. rdr.addEventListener ('error', () => alert ('Error loading file\n' + rdr.error));
  557. rdr.readAsText (f.files [0]);
  558. });
  559. document.body.appendChild (f);
  560. f.click ();
  561. document.body.removeChild (f);
  562. });
  563. AddLine (plane, e1, e2);
  564. }
  565. let int = setInterval (function () {
  566. if (!document.body)
  567. return;
  568. document.body.appendChild (back);
  569. document.body.appendChild (plane);
  570. clearInterval (int);
  571. }, 1);
  572. console.log ('Settings page created');
  573. return;
  574. }
  575. // apply settings
  576. function unwrap (x) {
  577. return x.wrappedJSObject || x;
  578. }
  579. function WaitForElement (select, check, attribs, callback) {
  580. const q = document.querySelector (select);
  581. if (q)
  582. return callback (q);
  583. const sett = { childList: true, subtree: true };
  584. if (attribs.length) {
  585. sett.attributes = true;
  586. sett.attributeFilter = attribs;
  587. }
  588. new MutationObserver ((list, mo) => {
  589. for (const rec of list)
  590. switch (rec.type) {
  591. case 'childList':
  592. for (const node of rec.addedNodes)
  593. if (node && node.nodeType === Node.ELEMENT_NODE && check (node)) {
  594. mo.disconnect ();
  595. return callback (node);
  596. }
  597. break;
  598. case 'attributes':
  599. if (check (rec.target)) {
  600. mo.disconnect ();
  601. return callback (rec.target);
  602. }
  603. break;
  604. }
  605. }).observe (document.body, sett);
  606. }
  607. let styles = '';
  608. const notifier = document.createElement ('span');
  609. if (true) {
  610. let url = undefined;
  611. let sea = undefined;
  612. let ev = undefined;
  613. notifier.notify = function (callback) {
  614. if (ev)
  615. callback (ev);
  616. notifier.addEventListener ('ytfix-urlchange', callback);
  617. };
  618. function ChangeDetect () {
  619. if (location.pathname === url && location.search === sea)
  620. return;
  621. url = location.pathname;
  622. sea = location.search;
  623. const modurl = url.replace (/^\/(user\/|channel\/|@\b)/, '/c/');
  624. console.log ('navigate to', url, sea);
  625. unwrap (document.body).setAttribute ('ytfix-url', modurl);
  626. ev = new CustomEvent ('ytfix-urlchange', { detail: modurl });
  627. notifier.dispatchEvent (ev);
  628. }
  629. document.addEventListener ('DOMContentLoaded', () => {
  630. ChangeDetect ();
  631. window.addEventListener ('popstate', ChangeDetect);
  632. window.addEventListener ('yt-navigate-start', ChangeDetect);
  633. });
  634. }
  635. if (settings.hide_guide) {
  636. let btn = null;
  637. let clicked = false;
  638. function Press () {
  639. if (btn && !clicked && btn.attributes ['aria-pressed'].value === 'true') {
  640. btn.click ();
  641. clicked = false;
  642. unwrap (window).dispatchEvent (new Event ('resize'));
  643. }
  644. }
  645. document.addEventListener (
  646. 'DOMContentLoaded',
  647. () => WaitForElement (
  648. 'yt-icon-button#guide-button.ytd-masthead > button[aria-pressed]',
  649. x => x.matches ('yt-icon-button#guide-button.ytd-masthead > button[aria-pressed]'),
  650. ['aria-pressed'],
  651. x => {
  652. btn = x;
  653. x.addEventListener ('click', () => { clicked = true; });
  654. new MutationObserver (Press).observe (x, { attributes: true, attributeFilter: ['aria-pressed'] });
  655. Press ();
  656. }
  657. )
  658. );
  659. window.addEventListener ('yt-navigate-finish', () => {
  660. clicked = false;
  661. Press ();
  662. });
  663. }
  664. if (settings.fix_removed_placeholder)
  665. styles += 'paper-button.style-blue-text,tp-yt-paper-button.style-blue-text{padding:0!important}';
  666. if (settings.thumbnail_size)
  667. styles += `
  668. body:not([ytfix-url^='/c/']) div#contents.ytd-rich-grid-renderer {display:block!important}
  669. body:not([ytfix-url^='/c/']) ytd-rich-grid-row.ytd-rich-grid-renderer {display:inline!important}
  670. body:not([ytfix-url^='/c/']) ytd-rich-grid-row.ytd-rich-grid-renderer > div {display:inline!important;margin:0!important}
  671. body:not([ytfix-url^='/c/']) ytd-rich-item-renderer:not(.YT-HWV-WATCHED-HIDDEN):not(.YT-HWV-SHORTS-HIDDEN) {display:inline-block!important;width:${[0, 180, 240, 360, 480, settings.thumbnail_size_m] [settings.thumbnail_size]}px!important;contain:none!important;margin-left:calc(var(--ytd-rich-grid-item-margin)/2)!important;margin-right:calc(var(--ytd-rich-grid-item-margin)/2)!important}
  672. `;
  673. if (settings.thumbnail_size_channel)
  674. styles += `
  675. body[ytfix-url^='/c/'] div#contents.ytd-rich-grid-renderer {display:block!important}
  676. body[ytfix-url^='/c/'] ytd-rich-grid-row.ytd-rich-grid-renderer {display:inline!important}
  677. body[ytfix-url^='/c/'] ytd-rich-grid-row.ytd-rich-grid-renderer > div {display:inline!important;margin:0!important}
  678. body[ytfix-url^='/c/'] ytd-rich-item-renderer:not(.YT-HWV-WATCHED-HIDDEN):not(.YT-HWV-SHORTS-HIDDEN) {display:inline-block!important;width:${[0, 180, 240, 360, 480, settings.thumbnail_size_channel_m] [settings.thumbnail_size_channel]}px!important;contain:none!important;margin-left:calc(var(--ytd-rich-grid-item-margin)/2)!important;margin-right:calc(var(--ytd-rich-grid-item-margin)/2)!important}
  679. `;
  680. if (settings.hide_yt_suggested_blocks)
  681. styles += `
  682. body:not([ytfix-url='/feed/subscriptions']) div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer:not(:first-child){display:none!important}
  683. body:not([ytfix-url^='/c/']):not([ytfix-url='/feed/subscriptions']) div#contents.ytd-rich-grid-renderer ytd-rich-section-renderer{display:none!important}
  684. `;
  685. if (settings.unfix_header)
  686. styles += `
  687. div#masthead-container.ytd-app,ytd-mini-guide-renderer.ytd-app,app-drawer#guide{position:absolute!important}
  688. ytd-feed-filter-chip-bar-renderer{position:relative!important}
  689. ytd-feed-filter-chip-bar-renderer:not([not-sticky]) > div#chips-wrapper{position:absolute!important;top:0!important}
  690. ytd-rich-grid-renderer > div#chips-wrapper{position:absolute!important;top:0!important}
  691. `;
  692. if (settings.search_thumbnail) {
  693. const sz = [0, 240, 360] [settings.search_thumbnail] + 'px!important';
  694. // min-width defaults to 240px, max-width defaults to 360px
  695. // sizes for: videos, playlists, channels, mixes
  696. 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,ytd-video-renderer[is-search] ytd-thumbnail.ytd-video-renderer,ytd-playlist-renderer[is-search] ytd-playlist-thumbnail.ytd-playlist-renderer,ytd-channel-renderer[is-search] #avatar-section.ytd-channel-renderer,ytd-radio-renderer[is-search] ytd-thumbnail.ytd-radio-renderer{min-width:${sz};max-width:${sz}}`;
  697. }
  698. if (settings.clear_search)
  699. styles += `
  700. ytd-two-column-search-results-renderer ytd-shelf-renderer.style-scope.ytd-item-section-renderer,
  701. ytd-two-column-search-results-renderer ytd-horizontal-card-list-renderer.style-scope.ytd-item-section-renderer,
  702. ytd-two-column-search-results-renderer ytd-reel-shelf-renderer.style-scope.ytd-item-section-renderer
  703. {display:none!important}
  704. `;
  705. styles += [
  706. '#player-theater-container{margin-left:auto!important;margin-right:auto!important}',
  707. '#player-container-outer{margin-left:0!important}',
  708. '#player-container-outer{margin-right:0!important}#player-theater-container{margin-left:auto!important}'
  709. ] [settings.align_player];
  710. const sizes = [0, 144, 240, 360, 480, 720];
  711. const size_norm = sizes [settings.default_player];
  712. if (size_norm)
  713. styles += `
  714. ytd-watch-flexy:not([fullscreen]):not([simple-fullscreen]):not([theater]){--ytd-watch-flexy-min-player-height:${size_norm}px!important;--ytd-watch-flexy-max-player-height:${size_norm}px!important;--ytd-watch-flexy-min-player-width:calc(${size_norm}px*var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))!important;--ytd-watch-flexy-max-player-width:var(--ytd-watch-flexy-min-player-width)!important}
  715. ytd-watch-flexy:not([fullscreen]):not([simple-fullscreen]):not([theater]) div#player-container-outer.ytd-watch-flexy{height:var(--ytd-watch-flexy-max-player-height);min-width:calc(${size_norm}px*var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))!important}
  716. `;
  717. const size_theater = sizes [settings.theater_player];
  718. if (size_theater)
  719. styles += `
  720. ytd-watch-flexy:not([fullscreen]):not([simple-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}
  721. ytd-watch-flexy:not([fullscreen]):not([simple-fullscreen])[theater] div#movie_player{height:${size_theater}px;width:calc(${size_theater}px*var(--ytd-watch-flexy-width-ratio)/var(--ytd-watch-flexy-height-ratio))}
  722. `;
  723. if (size_norm || size_theater)
  724. setInterval (function () {
  725. const eq = document.getElementsByTagName ("ytd-watch-flexy");
  726. if (!eq.length)
  727. return;
  728. const s = eq [0].hasAttribute ('theater') ? size_theater : size_norm;
  729. if (!s)
  730. return;
  731. const p = document.getElementById ("movie_player");
  732. if (!p)
  733. return;
  734. const ep = unwrap (p);
  735. if (ep.setInternalSize && ep.isFullscreen && ep.getPlayerSize && !ep.isFullscreen () && ep.getPlayerSize ().height != s)
  736. ep.setInternalSize ();
  737. }, 1000);
  738. if (settings.logo_target) {
  739. let url = settings.logo_target;
  740. if (url [0] != '/')
  741. url = '/' + url;
  742. url = location.origin + url;
  743. setInterval (function () {
  744. for (const E of document.querySelectorAll ('a#logo')) {
  745. const D = unwrap (E).data;
  746. if (D && D.commandMetadata && D.commandMetadata.webCommandMetadata && D.commandMetadata.webCommandMetadata.url !== url)
  747. D.commandMetadata.webCommandMetadata.url = url;
  748. if (E.href !== url)
  749. E.href = url;
  750. }
  751. }, 1000);
  752. }
  753. if (settings.channel_top)
  754. styles += 'app-header#header.style-scope.ytd-c4-tabbed-header-renderer{transform:none!important;position:absolute;left:0px!important;top:0px;margin-top:0px}';
  755. if (settings.channel_top > 1)
  756. styles += `
  757. div#contentContainer.style-scope.app-header-layout{padding-top:148px!important}
  758. div#contentContainer.style-scope.app-header{height:148px!important}
  759. div.banner-visible-area.style-scope.ytd-c4-tabbed-header-renderer{display:none!important}
  760. `;
  761. if (settings.try_load_more) {
  762. setInterval (function () {
  763. const l = document.querySelectorAll ('#show-more-button');
  764. let i = l.length;
  765. if (--i >= 0 && l [i].hasAttribute ('hidden')) {
  766. l [i].removeAttribute ('hidden');
  767. l [i].innerText = 'TRY LOAD MORE';
  768. }
  769. while (--i >= 0)
  770. l [i].parentNode.removeChild (l [i]);
  771. }, 1000);
  772. styles += '#show-more-button{color:var(--yt-spec-call-to-action);width:100%;text-align:center;border:1px solid;padding:1em;cursor:pointer}';
  773. }
  774. if (settings.unbound_video_title)
  775. styles += '#video-title{max-height:none!important;-webkit-line-clamp:none!important}';
  776. if (settings.video_quality) {
  777. function TryQuality (quality, qq, ep) {
  778. return qq.includes (quality) && (ep.setPlaybackQualityRange (quality, quality) || true);
  779. }
  780. let fail = '';
  781. setInterval (function () {
  782. const p = document.getElementById ("movie_player");
  783. if (!p)
  784. return;
  785. const ep = unwrap (p);
  786. if (!ep.getPreferredQuality || !ep.getAvailableQualityLevels || !ep.setPlaybackQualityRange || !ep.getVideoData || ep.getPreferredQuality () != 'auto')
  787. return;
  788. const vid = ep.getVideoData ().video_id;
  789. if (fail == vid) // данное видео уже обработано
  790. return;
  791. const qq = ep.getAvailableQualityLevels ();
  792. if (!qq || !qq.length)
  793. return;
  794. switch (settings.video_quality) { // intentional no breaks here
  795. case 1: if (TryQuality ('hd2160', qq, ep)) return;
  796. case 2: if (TryQuality ('hd1440', qq, ep)) return;
  797. case 3: if (TryQuality ('hd1080', qq, ep)) return;
  798. case 4: if (TryQuality ('hd720', qq, ep)) return;
  799. case 5: if (TryQuality ('large', qq, ep)) return;
  800. case 6: if (TryQuality ('medium', qq, ep)) return;
  801. case 7: if (TryQuality ('small', qq, ep)) return;
  802. case 8: if (TryQuality ('tiny', qq, ep)) return;
  803. }
  804. console.log ('Unknown video qualities in list: ', qq);
  805. fail = vid;
  806. }, 1000);
  807. }
  808. if (settings.resume_bar_handling)
  809. styles += [
  810. '',
  811. 'div.ytd-thumbnail-overlay-resume-playback-renderer{width:100%!important}',
  812. 'ytd-thumbnail-overlay-resume-playback-renderer{display:none!important}'
  813. ] [settings.resume_bar_handling];
  814. let watched_filter = '';
  815. if (settings.watched_grayscale)
  816. watched_filter += `grayscale(${settings.watched_grayscale}%)`;
  817. if (settings.watched_blur)
  818. watched_filter += `blur(${settings.watched_blur}px)`;
  819. if (watched_filter)
  820. styles += `a[href*="/watch?"]:not([href^="/watch?"]).ytd-thumbnail img {filter:${watched_filter}}`;
  821. if (settings.no_resume_time || watched_filter) {
  822. function removeTimesClearer (l) {
  823. while (l && l.tagName != 'A')
  824. l = l.parentNode;
  825. if (!l)
  826. return;
  827. if (!settings.no_resume_time || l.querySelector ('img') === null || l.parentNode.tagName === 'YTD-MACRO-MARKERS-LIST-ITEM-RENDERER') {
  828. l.href = l.href.replace (/&t=\d+s?/, '$1');
  829. return;
  830. }
  831. l.href = l.href.replace (/&t=\d+s?/, '');
  832. l = unwrap (l);
  833. if (!l.data || !l.data.watchEndpoint || !l.data.watchEndpoint.startTimeSeconds)
  834. return;
  835. delete l.data.watchEndpoint;
  836. try { l.data.commandMetadata.webCommandMetadata.url = l.href; } catch (ex) { }
  837. }
  838. setInterval (function () {
  839. document.querySelectorAll ('a[href^="/watch?"] div.ytd-thumbnail-overlay-resume-playback-renderer').forEach (removeTimesClearer); // основное применение
  840. document.querySelectorAll ('a[href^="/watch?"][href*="&t="]').forEach (removeTimesClearer); // на случай прочих ссылок
  841. }, 1000);
  842. }
  843. if (settings.remove_yt_redirect) {
  844. function removeRedirectClearer (l) {
  845. l.href = decodeURIComponent (l.href.replace (/^.*\?(.*&)q=([^&]+)(&.*)?$/, '$2'));
  846. const w = unwrap (l);
  847. if (w.data && w.data.urlEndpoint)
  848. w.data.urlEndpoint.url = l.href;
  849. }
  850. setInterval (function () {
  851. document.querySelectorAll ('a[href^="https://www.youtube.com/redirect?"]').forEach (removeRedirectClearer);
  852. }, 1000);
  853. }
  854. if (settings.view_count_mod === 2) {
  855. function replaceCountersText (x) {
  856. x = unwrap (x);
  857. const par = x.parentNode.__ytfix_parent;
  858. if (!par)
  859. return;
  860. try {
  861. const d = par.__data.data;
  862. const val = d.viewCountText.simpleText;
  863. if (val !== d.shortViewCountText.simpleText) {
  864. d.shortViewCountText.simpleText = val;
  865. d.shortViewCountText.accessibility.accessibilityData.label = val;
  866. x.textContent = val;
  867. }
  868. return;
  869. }
  870. catch (ex) { }
  871. try {
  872. const d = par.__data.data.content.videoRenderer;
  873. const val = d.viewCountText.simpleText;
  874. if (val !== d.shortViewCountText.simpleText) {
  875. d.shortViewCountText.simpleText = val;
  876. d.shortViewCountText.accessibility.accessibilityData.label = val;
  877. x.textContent = val;
  878. }
  879. return;
  880. }
  881. catch (ex) { }
  882. }
  883. function replaceCountersCallback (mm) {
  884. for (let i = mm.length; --i >= 0; )
  885. replaceCountersText (mm [i].target);
  886. }
  887. const m = new MutationObserver (replaceCountersCallback);
  888. const opt = { subtree: true, characterData: true };
  889. function replaceCountersEach (x) {
  890. const ee = x.querySelectorAll ('#metadata-line span');
  891. if (ee.length != 2)
  892. return;
  893. x.setAttribute ('ytfix', '');
  894. const e = ee [0];
  895. unwrap (e).__ytfix_parent = x;
  896. replaceCountersText (e.firstChild);
  897. m.observe (e, opt);
  898. }
  899. function replaceCountersText2 (x) {
  900. try {
  901. const vvcr = x.__dataHost.__data.videoPrimaryInfoRenderer.viewCount.videoViewCountRenderer;
  902. const val = vvcr.viewCount.simpleText;
  903. if (val !== vvcr.shortViewCount.simpleText) {
  904. vvcr.shortViewCount.simpleText = val;
  905. x.querySelectorAll ('span') [0].textContent = val;
  906. }
  907. return;
  908. }
  909. catch (ex) { }
  910. }
  911. function replaceCountersCallback2 (mm) {
  912. for (let i = mm.length; --i >= 0; )
  913. replaceCountersText2 (mm [i].target.parentNode);
  914. }
  915. const m2 = new MutationObserver (replaceCountersCallback2);
  916. const opt2 = { subtree: true, childList: true };
  917. function replaceCountersEach2 (x) {
  918. x.setAttribute ('ytfix', '');
  919. replaceCountersText2 (x);
  920. m2.observe (x, opt2);
  921. }
  922. setInterval (function () {
  923. document.querySelectorAll ('ytd-compact-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  924. document.querySelectorAll ('ytd-grid-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  925. document.querySelectorAll ('ytd-rich-item-renderer:not([ytfix])').forEach (replaceCountersEach);
  926. document.querySelectorAll ('ytd-video-renderer:not([ytfix])').forEach (replaceCountersEach);
  927. document.querySelectorAll ('yt-formatted-string#info:not([ytfix])').forEach (replaceCountersEach2);
  928. }, 1000);
  929. }
  930. if (settings.disable_player_click_overlay)
  931. styles += 'div.ytp-doubletap-ui,div.ytp-doubletap-ui-legacy {display:none}';
  932. if (settings.description_width) {
  933. const w = [0, '100vw', '1200px', '1280px', '1360px', '1440px', '1520px', '1600px', '1680px', '1760px', '1840px', '1920px'] [settings.description_width];
  934. styles += `
  935. ytd-app:not([mini-guide-visible_]) ytd-page-manager {--ytf-width:calc(${w} - 20px);min-width:100%}
  936. ytd-app[mini-guide-visible_] ytd-page-manager {--ytf-width:calc(${w} - 92px);min-width:calc(100% - 92px)}
  937. ytd-watch-flexy[flexy][fullscreen] {min-width:100%!important}
  938. 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}
  939. ytd-watch-flexy[flexy][is-two-columns_]:not([is-four-three-to-sixteen-nine-video_]):not([is-extra-wide-video_]) div#primary.ytd-watch-flexy {max-width:var(--ytd-watch-flexy-max-player-width);min-width:var(--ytd-watch-flexy-min-player-width)}
  940. ytd-watch-flexy[flexy][is-vertical-video_] div#player-container-inner.ytd-watch-flexy {width:calc(var(--ytd-watch-flexy-min-player-height)*1.7777777778);position:relative;transform:translate(-50%,0);margin-left:50%}
  941. `;
  942. }
  943. if (settings.simpler_fullscreen) {
  944. let keys = undefined;
  945. if (document.exitFullscreen)
  946. keys = document.fullscreenEnabled && { enter: 'requestFullscreen', exit: 'exitFullscreen', check: 'fullscreenElement', event: 'fullscreenchange', type: 'standart' };
  947. else if (document.mozCancelFullScreen)
  948. keys = document.mozFullScreenEnabled && { enter: 'mozRequestFullScreen', exit: 'mozCancelFullScreen', check: 'mozFullScreenElement', event: 'mozfullscreenchange', type: 'mozilla' };
  949. else if (document.webkitExitFullscreen)
  950. keys = document.webkitFullscreenEnabled && { enter: 'webkitRequestFullscreen', exit: 'webkitExitFullscreen', check: 'webkitFullscreenElement', event: 'webkitfullscreenchange', type: 'webkit' };
  951. else if (document.msExitFullscreen)
  952. keys = document.msFullscreenEnabled && { enter: 'msRequestFullscreen', exit: 'msExitFullscreen', check: 'msFullscreenElement', event: 'MSFullscreenChange', type: 'microsoft' };
  953. if (!keys)
  954. console.log ('unable to determine fullscreen API prefix or fullscreen API disabled');
  955. else {
  956. console.log ('detected fullscreen API type:', keys.type);
  957. styles += 'button.ytp-fullerscreen-edu-button{display:none!important}';
  958. document.addEventListener (keys.event, function () {
  959. const enter = !!document [keys.check];
  960. document.getElementById ("movie_player").setFauxFullscreen (enter);
  961. for (const q of document.querySelectorAll ('ytd-watch-flexy'))
  962. q [enter ? 'setAttribute' : 'removeAttribute'] ('simple-fullscreen', '');
  963. });
  964. setInterval (function () {
  965. for (const p of document.querySelectorAll ('button.ytp-fullscreen-button:not([ytfix])')) {
  966. const q = document.createElement ('button');
  967. q.className = p.className;
  968. p.parentNode.appendChild (q);
  969. q.appendChild (p.querySelector ('svg'));
  970. q.setAttribute ('title', 'Full screen');
  971. p.style.display = 'none';
  972. p.disabled = true;
  973. p.setAttribute ('ytfix', '');
  974. q.setAttribute ('ytfix', '');
  975. q.addEventListener ('mousedown', function (event) {
  976. event.preventDefault ();
  977. });
  978. q.addEventListener ('click', function (event) {
  979. if (document [keys.check])
  980. document [keys.exit] ();
  981. else {
  982. let w = event.target;
  983. while (w && w.tagName != "YTD-PLAYER")
  984. w = w.parentNode;
  985. if (w)
  986. w [keys.enter] ();
  987. }
  988. });
  989. }
  990. }, 1000);
  991. }
  992. }
  993. if (settings.clear_link_pp) {
  994. function clearLink (l) {
  995. l.setAttribute ('href', l.getAttribute ('href').replace (/&pp=[^&]*/, ''));
  996. }
  997. setInterval (function () {
  998. document.querySelectorAll ('a[href*="/watch?"][href*="&pp="]').forEach (clearLink);
  999. }, 1000);
  1000. }
  1001. if (settings.exact_likes) {
  1002. function conv_exact (x) {
  1003. if (x.length < 4)
  1004. return x;
  1005. if (x.length < 7)
  1006. return x.slice (0, -3) + '\u00A0' + x.slice (-3);
  1007. if (x.length < 10)
  1008. return x.slice (0, -6) + '\u00A0' + x.slice (-6, -3) + '\u00A0' + x.slice (-3);
  1009. return x.slice (0, -9) + '\u00A0' + x.slice (-9, -6) + '\u00A0' + x.slice (-6, -3) + '\u00A0' + x.slice (-3);
  1010. }
  1011. const tcdef = Object.getOwnPropertyDescriptor (Node.prototype, 'textContent');
  1012. function ItemProcess (x) {
  1013. if (unwrap (x).__data.data.targetId !== 'watch-like')
  1014. return;
  1015. (x.shadowRoot || x).querySelectorAll ('yt-formatted-string').forEach (s => {
  1016. const r = unwrap (s).__data.text;
  1017. const likesTxt = conv_exact (r.accessibility.accessibilityData.label.replace (/[^\d]/g, '') || '0');
  1018. if (tcdef.get.call (s) !== likesTxt) {
  1019. r.simpleText = likesTxt;
  1020. tcdef.set.call (s, likesTxt);
  1021. }
  1022. });
  1023. x.querySelectorAll ('yt-button-shape').forEach (s => {
  1024. const likesTxt = conv_exact (unwrap (s).__data.data.accessibilityLabel.replace (/[^\d]/g, '') || '0');
  1025. s.querySelectorAll ('span[role=text]').forEach (t => {
  1026. const s = unwrap (t);
  1027. if (tcdef.get.call (s) !== likesTxt)
  1028. tcdef.set.call (s, likesTxt);
  1029. });
  1030. });
  1031. }
  1032. setInterval (function () {
  1033. document.querySelectorAll ('ytd-menu-renderer.ytd-video-primary-info-renderer').forEach (q => {
  1034. (q.shadowRoot || q).querySelectorAll ('ytd-toggle-button-renderer').forEach (ItemProcess);
  1035. });
  1036. document.querySelectorAll ('ytd-menu-renderer.ytd-watch-metadata ytd-toggle-button-renderer').forEach (ItemProcess);
  1037. }, 1000);
  1038. }
  1039. if (settings.disable_video_preview)
  1040. styles += `
  1041. ytd-thumbnail-overlay-time-status-renderer { display: inline-flex !important; }
  1042. ytd-video-preview, div#hover-overlays { display: none !important; }
  1043. `;
  1044. if (settings.hide_engagement_panel_transcript)
  1045. styles += 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-transcript"] { display: none !important; }';
  1046. if (settings.hide_engagement_panel_structured_description) {
  1047. styles += 'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-structured-description"] { display: none !important; }';
  1048. const refs = [];
  1049. setInterval (function () {
  1050. document.querySelectorAll ('ytd-video-secondary-info-renderer tp-yt-paper-button#more').forEach (function (q) {
  1051. for (const w of refs)
  1052. if (w === q)
  1053. return;
  1054. refs.push (q);
  1055. q.addEventListener ('click', function (ev) {
  1056. let el = ev.target;
  1057. while (el && el.tagName !== 'YTD-EXPANDER')
  1058. el = el.parentNode;
  1059. if (el)
  1060. el.removeAttribute ('collapsed');
  1061. });
  1062. });
  1063. }, 1000);
  1064. }
  1065. if (settings.short_to_full2 === 2) {
  1066. let ld = await storage.load ('from_url');
  1067. function process () {
  1068. if (location.pathname.length !== 19 || !location.pathname.startsWith ('/shorts/'))
  1069. return;
  1070. const h = unwrap (window).history;
  1071. const state = h.state;
  1072. const videoId = location.pathname.substr (8);
  1073. const url = '/watch?v=' + videoId;
  1074. try {
  1075. state.endpoint.commandMetadata.webCommandMetadata.url = url;
  1076. state.endpoint.commandMetadata.webCommandMetadata.webPageType = 'WEB_PAGE_TYPE_WATCH';
  1077. state.endpoint.watchEndpoint = { nofollow: true, videoId: videoId };
  1078. delete state.endpoint.reelWatchEndpoint;
  1079. }
  1080. catch (ex) { }
  1081. h.replaceState (state, '', url);
  1082. storage.save ('from_url', location.pathname);
  1083. unwrap (window).dispatchEvent (new PopStateEvent ('popstate', { state: state }));
  1084. };
  1085. if (location.pathname !== ld)
  1086. process ();
  1087. notifier.addEventListener ('ytfix-urlchange', process);
  1088. }
  1089. if (settings.short_to_full2 === 1) {
  1090. let ld = await storage.load ('from_url');
  1091. setInterval (function () {
  1092. if (location.pathname === ld)
  1093. return;
  1094. if (ld)
  1095. storage.save ('from_url', ld = undefined);
  1096. if (location.pathname.length !== 19 || !location.pathname.startsWith ('/shorts/'))
  1097. return;
  1098. storage.save ('from_url', location.pathname);
  1099. location.replace ('/watch?v=' + location.pathname.substr (8));
  1100. }, 1000);
  1101. }
  1102. if (settings.view_count_mod === 0)
  1103. styles += `
  1104. span.view-count.ytd-video-view-count-renderer {display:none!important}
  1105. span.short-view-count.ytd-video-view-count-renderer {display:block!important}
  1106. `;
  1107. if (settings.hide_metadata)
  1108. styles += 'ytd-metadata-row-container-renderer {display:none!important}';
  1109. if (settings.video_shelves)
  1110. styles += "body[ytfix-url='/watch'] ytd-reel-shelf-renderer {display:none!important}";
  1111. // update notifications
  1112. const script_name = gminfo && gminfo.script && gminfo.script.name;
  1113. if (fix_version && load_version && storage.save && settings.update_alert && script_name) {
  1114. const m_fix = fix_version.match (/^(\d+)\.(\d+)\.(\d+)$/);
  1115. const m_load = load_version.match (/^(\d+)\.(\d+)\.(\d+)$/);
  1116. if (m_fix && m_load) {
  1117. if (m_fix [1] != m_load [1]) {
  1118. storage.save ('settings', settings);
  1119. alert (`Script "${script_name}" updated major version.\nView settings page to check if all options were transferred correctly.\n\nTo disable this alert go to 'Script' tab on settings page.`);
  1120. }
  1121. else if (settings.update_alert >= 2 && m_fix [2] != m_load [2]) {
  1122. storage.save ('settings', settings);
  1123. alert (`Script "${script_name}" updated version.\nCheck settings page for new options.\n\nTo disable this alert go to 'Script' tab on settings page.`);
  1124. }
  1125. else if (settings.update_alert == 3 && m_fix [3] != m_load [3]) {
  1126. storage.save ('settings', settings);
  1127. alert (`Script "${script_name}" released bugfix.\n\nTo disable this alert go to 'Script' tab on settings page.`);
  1128. }
  1129. }
  1130. }
  1131. if (settings.remove_rounded_corners_1)
  1132. styles += `
  1133. ytd-thumbnail a.ytd-thumbnail, ytd-thumbnail::before {border-radius:0!important}
  1134. ytd-playlist-thumbnail a.ytd-playlist-thumbnail, ytd-playlist-thumbnail::before {border-radius:0!important}
  1135. ytd-channel-video-player-renderer[rounded] #player.ytd-channel-video-player-renderer {border-radius:0!important}
  1136. ytd-watch-metadata[modern-metapanel] #description.ytd-watch-metadata {border-radius:0!important}
  1137. ytd-rich-metadata-renderer[rounded] {border-radius:0!important}
  1138. div.image-wrapper.ytd-hero-playlist-thumbnail-renderer {border-radius:0!important}
  1139. div.ytp-videowall-still-image, .ytp-ce-video.ytp-ce-large-round {border-radius:0!important}
  1140. `;
  1141. if (settings.channel_default) {
  1142. // default, VIDEOS, SHORTS, LIVE, PLAYLISTS, COMMUNITY, CHANNELS, ABOUT
  1143. const e_url = [undefined, '/videos', '/shorts', '/streams', '/playlists', '/community', '/channels', '/about'] [settings.channel_default];
  1144. function PauseVideo () {
  1145. document.querySelectorAll ('video').forEach (x => {
  1146. x = unwrap (x);
  1147. (x.yns_pause || x.pause).call (x);
  1148. });
  1149. }
  1150. function process () {
  1151. if (!/^\/c\/[^\/]+(\/(featured)?)?$/.test (unwrap (document.body).getAttribute ('ytfix-url')))
  1152. return;
  1153. try {
  1154. for (const { tabRenderer: tr } of unwrap (document.querySelectorAll ('ytd-app') [0]).__data.data.response.contents.twoColumnBrowseResultsRenderer.tabs) {
  1155. if (!tr)
  1156. continue;
  1157. const ep = tr.endpoint;
  1158. if (ep.commandMetadata.webCommandMetadata.url.endsWith (e_url)) {
  1159. const state = { endpoint: ep, savedComponentState: { }, entryTime: 1 };
  1160. unwrap (window).history.replaceState (state, '', ep.commandMetadata.webCommandMetadata.url);
  1161. unwrap (window).dispatchEvent (new PopStateEvent ('popstate', { state: state }));
  1162. setTimeout (PauseVideo, 100);
  1163. return;
  1164. }
  1165. }
  1166. }
  1167. catch (ex) {
  1168. setTimeout (process, 100);
  1169. }
  1170. };
  1171. notifier.notify (process);
  1172. }
  1173. if (settings.clear_sign)
  1174. setInterval (() => {
  1175. for (const e of document.querySelectorAll ('a[href*="$1"]'))
  1176. e.setAttribute ('href', e.getAttribute ('href').replace (/\$1$/, ''));
  1177. }, 1000);
  1178. if (settings.main_align)
  1179. styles += `body[ytfix-url='/'] div#contents {text-align:${['','left','center','right'][settings.main_align]}}`;
  1180. if (settings.subscriptions_align)
  1181. styles += `body[ytfix-url='/feed/subscriptions'] div#contents {text-align:${['','left','center','right'][settings.subscriptions_align]}}`;
  1182. // "settings" button
  1183. // can't store created button: Polymer overrides it's content on soft reload leaving tags in place
  1184. // but can store element that Polymer does not know how to deal with and just drops
  1185. let settingsMark = { parentNode: false };
  1186. setInterval (function () {
  1187. if (settingsMark.parentNode)
  1188. return;
  1189. let toolBar = document.getElementsByTagName ('ytd-topbar-menu-button-renderer');
  1190. if (!toolBar.length)
  1191. return;
  1192. toolBar = toolBar [0];
  1193. if (!toolBar)
  1194. return;
  1195. toolBar = toolBar.parentNode;
  1196. const sb = document.createElement ('ytd-topbar-menu-button-renderer'); // ytd-notification-topbar-button-renderer
  1197. sb.className = 'style-scope ytd-masthead style-default'; // style-scope ytd-masthead notification-button-style-type-default
  1198. sb.setAttribute ('use-keyboard-focused', '');
  1199. sb.setAttribute ('is-icon-button', '');
  1200. sb.setAttribute ('has-no-text', '');
  1201. const tbo = unwrap (toolBar);
  1202. const sbo = unwrap (sb);
  1203. tbo.insertBefore (sbo, tbo.childNodes [0]);
  1204. // div[id=notification-count][class=style-scope ytd-notification-topbar-button-renderer][innerHTML=...]
  1205. const mark = document.createElement ('fix-settings-mark');
  1206. mark.style = 'display:none';
  1207. toolBar.insertBefore (mark, sb); // must be added to parent node of buttons in order to Polymer dropped it on soft reload
  1208. settingsMark = mark;
  1209. const icb = document.createElement ('yt-icon-button');
  1210. icb.id = 'button';
  1211. icb.className = 'style-scope ytd-topbar-menu-button-renderer style-default';
  1212. const tt = document.createElement ('tp-yt-paper-tooltip');
  1213. tt.className = 'style-scope ytd-topbar-menu-button-renderer';
  1214. tt.setAttribute ('role', 'tooltip');
  1215. tt.setAttribute ('tabindex', '-1');
  1216. tt.style = 'right:auto;bottom:auto';
  1217. tt.appendChild (document.createTextNode ('YT fixes ' + fix_version)); // YT wraps content into DIV element
  1218. const aa = document.createElement ('a');
  1219. aa.className = 'yt-simple-endpoint style-scope ytd-topbar-menu-button-renderer';
  1220. aa.setAttribute ('tabindex', '-1');
  1221. aa.href = '/fix-settings';
  1222. aa.appendChild (icb);
  1223. aa.appendChild (tt);
  1224. sbo.getElementsByTagName ('div') [0].appendChild (unwrap (aa)); // created by YT scripts
  1225. const bb = icb.getElementsByTagName ('button') [0]; // created by YT scripts
  1226. bb.setAttribute ('aria-label', 'fixes settings');
  1227. const ic = document.createElement ('yt-icon');
  1228. ic.className = 'style-scope ytd-topbar-menu-button-renderer';
  1229. bb.appendChild (ic);
  1230. const gpath = document.createElementNS ('http://www.w3.org/2000/svg', 'path');
  1231. gpath.className.baseVal = 'style-scope yt-icon';
  1232. 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');
  1233. const svgg = document.createElementNS ('http://www.w3.org/2000/svg', 'g');
  1234. svgg.className.baseVal = 'style-scope yt-icon';
  1235. svgg.appendChild (gpath);
  1236. const svg = document.createElementNS ('http://www.w3.org/2000/svg', 'svg');
  1237. svg.className.baseVal = 'style-scope yt-icon';
  1238. svg.setAttributeNS (null, 'viewBox', '0 0 24 24');
  1239. svg.setAttributeNS (null, 'preserveAspectRatio', 'xMidYMid meet');
  1240. svg.setAttribute ('focusable', 'false');
  1241. svg.setAttribute ('style', 'pointer-events: none; display: block; width: 100%; height: 100%;');
  1242. svg.appendChild (svgg);
  1243. (ic.shadowRoot || ic).appendChild (svg); // YT clears *ic
  1244. }, 1000);
  1245. // styles
  1246. if (styles.length) {
  1247. let styles_int = setInterval (function () {
  1248. if (!document.head)
  1249. return;
  1250. clearInterval (styles_int);
  1251. if (document.getElementById ('ytfixstyle'))
  1252. return;
  1253. const style_element = document.createElement ('style');
  1254. style_element.setAttribute ('type', 'text/css');
  1255. style_element.setAttribute ('id', 'ytfixstyle');
  1256. style_element.innerHTML = styles;
  1257. document.head.appendChild (style_element);
  1258. }, 100);
  1259. }
  1260. console.log ('Fixes loaded');
  1261. }) ();

QingJ © 2025

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