Greasy Fork is available in English.

YouTube Video Preview and Ratings Keyless

Instant video previews in popup player by hovering or clicking video thumbs. Video ratings and resolution data shown on the thumbs. Gets video information without using a YouTube API key, which can be banned or limited.

  1. // ==UserScript==
  2. // @name YouTube Video Preview and Ratings Keyless
  3. // @namespace YouTubeVideoPreviewPlayer
  4. // @version 20230327
  5. // @description Instant video previews in popup player by hovering or clicking video thumbs. Video ratings and resolution data shown on the thumbs. Gets video information without using a YouTube API key, which can be banned or limited.
  6. // @author Couchy
  7. // @match https://www.youtube.com/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @noframes
  11. // ==/UserScript==
  12.  
  13. //==================================================================
  14. //Userscript specific functions
  15.  
  16. function debug(...args) {
  17. //console.log(...args);
  18. }
  19.  
  20. function set_pref(preference, new_value) {
  21. GM_setValue(preference, new_value);
  22. }
  23.  
  24. function get_pref(preference) {
  25. return GM_getValue(preference);
  26. }
  27.  
  28. function init_pref(preference, new_value) {
  29. let value = get_pref(preference);
  30. if (value == null) {
  31. set_pref(preference, new_value);
  32. value = new_value;
  33. }
  34. return value;
  35. }
  36.  
  37. //==================================================================
  38. // Styles
  39.  
  40. const style_basic = `
  41. /* prefs */
  42. #vpp_pref_popup {direction:ltr; font:11px/11px Roboto,arial,sans-serif; position:fixed; right:0px; top:0px; color:#e0e0e0; background:#202020; padding:15px 15px 15px 10px; border-radius:3px; box-shadow:0px 0px 5px 1px gray; /*z-index:2147483647;*/ z-index:2147483646;}
  43. .vpp_pref_group {margin-left:15px; color:yellow;}
  44. #vpp_pref_close {font:14px/14px Roboto,arial,sans-serif; color:lightgray; position:absolute; top:3px; right:5px; cursor:pointer; user-select:none; -moz-user-select:none;}
  45. #vpp_pref_close:hover {color:white;}
  46. #vpp_pref_title {font:500 13px/13px Roboto,arial,sans-serif; padding:5px !important;}
  47. #vpp_pref_button {cursor:pointer; width:18px; height:18px; background-size:contain; background-repeat:no-repeat; opacity:0.7; position:absolute; right:0px; top:0px; user-select:none; -moz-user-select:none;}
  48. #vpp_pref_button:hover {opacity:1;}
  49. #vpp_pref_button {background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAeUlEQVR42u2UwQ2AMAwDk52YCWaCmdgp7QshKP6QKn7Yv0pudEqcuBXLBXB/7GaBzFv3Z3k+AdYY/z3cr+IZHgywPCZzxrv4T48AyAFAeKaH0ICmr2GFuDpAkQGOLRBAOQAIjw5Rtrg6QJEBji0QQDkACI8OUbYE0ADbonowcNn6sgAAAABJRU5ErkJggg==');}
  50. #vpp_pref_delay_text {margin-left:20px; color:khaki;}
  51. #vpp_pref_delay_num {margin-left:3px; color:khaki;}
  52. .vpp_pref_delay_plusminus {margin-left:5px; cursor:pointer; text-align:center; font-weight:800; color:black; background:#B0B0B0; border-radius:2px; display:inline-block; width:10px; user-select:none; -moz-user-select:none;}
  53. .vpp_pref_delay_plusminus:hover {background:#D0D0D0;}
  54. #vpp_pref_delay_plus {margin-left:2px;}
  55. #vpp_pref_delay_minus {margin-left:5px;}
  56. /* meta data */
  57. .vpp_meta_def_container {font:500 12px/14px Roboto,arial,sans-serif; position:absolute; top:0px; left:0px; background:#e8e8e8; padding:0px 3px; border-radius:2px; display:none; cursor:default;}
  58. html[dark] .vpp_meta_def_container {background:#202020 !important;}
  59. .vpp_meta_def_container[reveal] {display: block;}
  60. .vpp_meta_def_container[space] .vpp_meta_def_hd {margin-right:2px;}
  61. .vpp_meta_def_format {position:relative; color:black;}
  62. html[dark] .vpp_meta_def_format {color:#f0f0f0 !important;}
  63. .vpp_meta_def_hd {position:relative;}
  64. .vpp_meta_def_hd.HD {color:magenta;}
  65. .vpp_meta_def_hd.UHD {color:red;}
  66. .vpp_meta_rate {direction:ltr; font:500 12px/14px Roboto,arial,sans-serif; position:absolute; top:0px; right:0px; color:white; padding:0px 3px; border-radius:2px; cursor:default;}
  67. body[dir='ltr'] [vpp_meta_rate] ytd-thumbnail-overlay-toggle-button-renderer, body[dir='rtl'] [vpp_meta_def] ytd-thumbnail-overlay-toggle-button-renderer {margin-top:12px !important;}
  68. #vpp_meta_box {position:relative; float:left; height:13px; margin-top:3px;}
  69. #vpp_meta_box .vpp_meta_def_container {background:none !important; position:relative; clear:none; float:left;}
  70. #vpp_meta_box .vpp_meta_rate {position:relative; clear:none; float:right; margin-left:5px;}
  71. #gridtube_title_container {position:absolute; top:5px; right:5px;}
  72. body[dir='rtl'] #gridtube_title_container {left:5px !important; right:auto !important;}
  73. body ytd-video-primary-info-renderer {position:relative !important;}
  74. /* play button*/
  75. #vpp_now_playing {font:500 14px/14px Roboto,arial,sans-serif; position:absolute; bottom:0px; left:0px; background:red; color:white; padding:5px; cursor:default; z-index:0;}
  76. body[dir='rtl'] #vpp_now_playing {left:auto !important; right:0px !important;}
  77. body[vpp_reveal_play_button] .vpp_play_button, *[vpp_play_marked]:hover .vpp_play_button {visibility:visible !important;}
  78. .vpp_play_button_container {position:absolute; bottom:0px; left:0px; width:100%;}
  79. .vpp_play_button {display:block !important; position:relative !important; padding-bottom:1px !important; margin:0px auto !important; width:25px !important; height:25px !important;
  80. opacity:0.75 !important; cursor:pointer !important; background-size:25px !important; background-repeat:no-repeat !important; text-decoration:none !important; z-index:1; user-select:none; -moz-user-select:none;}
  81. .vpp_play_button:hover {opacity:1 !important;}
  82. .vpp_play_button {background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAG7AAABuwBHnU4NQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAOqSURBVFiF5ZdPSBxnGMZ/M98s1V1nV01A17VZUwMRqsGY9FyQlBJQ2gYPIbaB0nMgwYZWeumxIQhlT7aQW0Mhl54CvQQiISQhkixN\
  83. bWKXaNSaYuzu6v4Z98+38/WgY9Vu9l835NAHvsvM983zvs/7vPPOaEopXif018oOGAAfjY4GgEvAu0DnK+b8A5gCvvjp6tVl7cMzZ94EwkArgM/n42BXF7peX3Fs22b+2TPW19edSzGg3wAmgFbTNPlybIyDBw7UlXgv5hcX+WZigmQy2QpMiJ6+vhDQ9Pn587wVDGLb9itdXtOku7ubW7dvA+wzgHa3201XMEg2nyebzyOlpN69oQGG\
  84. YfCGy0VXMIjb7cayrHYDoDMQIJPLkUynKdh2nal3Q+g6psdDZyDA75HIZhcoIJFKIQuFkofz2SxCCHTDqDkA27ZJpFLbCusAuVyOvJQopUquWDzO5OQkfy4vl91bauWlJJfL/ROAlLJiEy0sLfFtKMTP16+TsayazSilBLZeRI405aBsm8JWmW7cvMn0gweMnDrFocOHay7JpgeUqiiAQqGwHQBALB7n+ytXONLXx/DQEKbPVzGxM4Oq\
  85. U0CpXQE4eBgOMzMzwwfDwwwcO1aVSatSwN5Rgr1TVErJj9eucefuXUZGRmjz+8smAzumYUXmKRSQUiKl3C7H3vV0fp5Lly9za2oKrcRzdylQLKOiCkDREuzFe4ODfDo6yloqxUo0WnJvzSUohg6/n/GxMXp6eogsLJDd6vViqM2ELwlACMHHp0/z2dmzLK2s8MvsbNlnOai6DZ0XiIMjvb18PT6Ox+vl4ZMnFZXI4dwOAKpvwyaPhwvn\
  86. zjF08iSzc3Msrq5WRLwX28OoGg+8f+IEX128SMKyuBcO1zS6nTObClRYAndjI9+FQvT29vJbJMJGNlsDtRPBjhLouo6tFFqZM/v278dsamL60aPaidnM3vnmNIQQaEKwYVk0NDSUPJhMp0mm0/+JHCCTyaALgcswMLymqWwptfVEgkwmg9vtRgiBppXTozo4BrYsi2wuh7JtvF6vMjra21+sRqNtqVSKtKYRi8frSvyyYDSgw+9/YRw/\
  87. evTpnfv329aiUQyXC+qceRF2lG3T0tzM8f7+OW3m8eP++NrajXvT061SSqRSZb8Ny6FoCpqGEIIGw8DlcvHOwEC82ecb1JRS/BWLvZ1MJn/I5/N+lGpUOwxQSo+SPtm6968dmrbhEuK51zQ/aWlp+VX73/8d/w3y7NP9Di2fPgAAAABJRU5ErkJggg==') !important;}
  88. /* preview player */
  89. #vpp_player_area {position:fixed; width:100%; height:100%; top:0px; left:0px; background:rgba(0,0,0,0.5); overflow:hidden !important; z-index:2147483646 !important;}
  90. #vpp_player_area2 {position:relative; width:100%; height:100%; visibility:hidden;}
  91. #vpp_player_box {position:absolute; background:#606060; box-shadow:0px 0px 8px 3px rgba(128,128,128,0.9); border-radius:5px 5px 0px 0px; max-width:100%; max-height:100%;}
  92. #vpp_player_box[player_pos='00']:not([player_size='fit']) {top:0px; left:0px;}
  93. #vpp_player_box[player_pos='01']:not([player_size='fit']) {top:0px; left:0px; right:0px; margin:auto;}
  94. #vpp_player_box[player_pos='02']:not([player_size='fit']) {top:0px; right:0px;}
  95. #vpp_player_box[player_pos='10']:not([player_size='fit']) {top:0px; bottom:0px; left:0px; margin:auto;}
  96. #vpp_player_box[player_pos='11']:not([player_size='fit']) {top:0px; bottom:0px; left:0px; right:0px; margin:auto;}
  97. #vpp_player_box[player_pos='12']:not([player_size='fit']) {top:0px; bottom:0px; right:0px; margin:auto;}
  98. #vpp_player_box[player_pos='20']:not([player_size='fit']) {bottom:0px; left:0px;}
  99. #vpp_player_box[player_pos='21']:not([player_size='fit']) {bottom:0px; left:0px; right:0px; margin:auto;}
  100. #vpp_player_box[player_pos='22']:not([player_size='fit']) {bottom:0px; right:0px;}
  101. #vpp_player_box[player_pos='right']:not([player_size='fit']) {float:right;}
  102. #vpp_player_box[player_size='xxsmall'] {/*320x180*/ width:320px; height:200px;}
  103. #vpp_player_box[player_size='xsmall'] {/*512x288*/ width:512px; height:308px;}
  104. #vpp_player_box[player_size='small'] {/*768x432*/ width:768px; height:452px;}
  105. #vpp_player_box[player_size='medium'] {/*1024x576*/ width:1024px; height:596px;}
  106. #vpp_player_box[player_size='large'] {/*1280x720*/ width:1280px; height:740px;}
  107. #vpp_player_box[player_size='xlarge'] {/*1600x900*/ width:1600px; height:920px;}
  108. #vpp_player_box[player_size='xxlarge'] {/*1920x1080*/ width:1920px; height:1100px;}
  109. #vpp_player_box[player_size='fit'] {width:100%; height:100%; border-radius:0px !important;}
  110. #vpp_player_holder {position:relative; width:100%; height:100%;}
  111. #vpp_player_holder2 {position:absolute; top:20px; left:0px; right:0px; bottom:0px; margin:auto; background:black;}
  112. #vpp_player_box[player_size='fit'] #vpp_player_holder2 {left:0px !important; right:0px !important;}
  113. #vpp_player_frame {position:relative; width:100%; height:100%; display:block; border:0px;}
  114. #vpp_player_button_area_top {direction:ltr; font:500 14px/20px Roboto,arial,sans-serif; color:#101010; position:absolute; top:-2px; left:10px;}
  115. #vpp_player_button_area_next {font:500 19px/20px Roboto,arial,sans-serif; color:#101010; position:absolute; top:0px; right:35px;}
  116. .vpp_player_button {position:relative; cursor:pointer; padding:0px 5px; user-select:none; -moz-user-select:none;}
  117. .vpp_player_button[button_kind='plus'], .vpp_player_button[button_kind='minus'] {font:500 20px/20px Roboto,arial,sans-serif !important; top:2px;}
  118. .vpp_player_button[button_kind='left'] {padding:0px 2px 0px 5px;}
  119. .vpp_player_button[button_kind='right'] {padding:0px 2px;}
  120. .vpp_player_button[button_kind='up'] {padding:0px 2px;}
  121. .vpp_player_button[button_kind='down'] {padding:0px 5px 0px 2px;}
  122. .vpp_player_button:hover {color:#E0E0E0;}
  123. #vpp_player_close_mark {font:14px/20px Roboto,arial,sans-serif; position:absolute; top:0px; right:5px; cursor:pointer; user-select:none; -moz-user-select:none;}
  124. #vpp_player_close_mark:hover {color:#E0E0E0;}\
  125. /* float preview */
  126. #vpp_float_box {position:absolute; box-shadow:0px 0px 8px 3px rgba(128,128,128,0.9); background:black; z-index:2147483647;}
  127. #vpp_float_frame {position:relative; width:100%; height:100%; border:0px;}
  128. /* player options */
  129. #vpp_player_options_popup {direction:ltr; position:absolute; left:0px; top:0px; font:11px/11px Roboto,arial,sans-serif; color:white; background:linear-gradient(#888888,#787878); padding:5px; border-radius:5px; /*z-index:2147483647;*/ z-index:2147483646;}
  130. .vpp_player_options_text {font-weight:500; margin-left:5px; margin-top:7px; color:lemonchiffon;}
  131. .vpp_player_options_close {font:14px/14px Roboto,arial,sans-serif; color:black; position:absolute; top:3px; right:5px; cursor:pointer; user-select:none; -moz-user-select:none;}
  132. .vpp_player_options_close:hover {color:lightgray;}
  133. .vpp_player_options_title {font:500 13px/13px Roboto,arial,sans-serif; padding:3px !important; color:lemonchiffon;}
  134. /*other*/
  135. .watched .video-thumb {opacity:1 !important;}
  136. .yt-subscribe-button-right {margin-top:12px !important;}
  137. .pl-video .pl-video-thumbnail,
  138. .pl-video .pl-video-thumb,
  139. .pl-video .yt-thumb {width: 120px !important;}
  140. .pl-video .yt-thumb-clip > img {width:120px !important; height:auto !important;}
  141. .pl-video-time .timestamp {padding-top:18px !important;}
  142. `;
  143.  
  144.  
  145. //==============================================================
  146. //basic
  147.  
  148. const AREA_ID = "vpp_player_area";
  149. const BOX_ID = "vpp_player_box";
  150. const HOLDER_ID = "vpp_player_holder";
  151.  
  152. function newElem(tag, attrs = {}, text = null, parent = null) {
  153.  
  154. const node = document.createElement(tag);
  155. for (let attr in attrs) {
  156. node.setAttribute(attr, attrs[attr]);
  157. }
  158. if (text) {
  159. node.textContent = text;
  160. }
  161.  
  162. parent?.appendChild(node);
  163.  
  164. return node;
  165. }
  166.  
  167. function insertStyle(str, id, doc = document) {
  168. const style = document.getElementById(id);
  169. if (style) {
  170. style.textContent = str;
  171. }
  172. else {
  173. newElem("style", {"type": "text/css", "id": id}, str, document.head);
  174. }
  175. }
  176.  
  177. function injectScript(str, src, doc = document) {
  178. if (str) {
  179. document.head.removeChild(newElem("script", {}, str, document.head));
  180. }
  181. else if (src) {
  182. newElem("script", {"src": src}, null, document.head)
  183. }
  184. }
  185.  
  186. function xpath(outer_dom, inner_dom, query) {
  187. //XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7
  188. return outer_dom.evaluate(query, inner_dom, null, 7, null);
  189. }
  190.  
  191.  
  192. function docsearch(query) {
  193. return xpath(document, document, query);
  194. }
  195.  
  196.  
  197. function innersearch(inner, query) {
  198. return xpath(document, inner, query);
  199. }
  200.  
  201.  
  202. function simulClick(el) {
  203. const clickEvent = document.createEvent("MouseEvents");
  204. clickEvent.initEvent("click", true, true);
  205. clickEvent.artificialevent = true;
  206. el.dispatchEvent(clickEvent);
  207. }
  208.  
  209. function filter(str, w, delim) {
  210. if (str) {
  211. const m = str.match(RegExp(`[${delim}]${w}[^${delim}]*`));
  212. if (m != null) {
  213. return m[0].replace(RegExp(`[${delim}]${w}`), "");
  214. }
  215. }
  216. return null;
  217. }
  218.  
  219. //==============================================================
  220. //preferences
  221.  
  222. var pref_floatEnable = init_pref("floatEnable", true);
  223. var pref_floatDelay = init_pref("floatDelay", 1);
  224. var pref_playerEnable = init_pref("playerEnable", true);
  225. var pref_rateEnable = init_pref("rateEnable", true);
  226. var pref_defEnable = init_pref("defEnable", true);
  227.  
  228. function new_plusminus(prefname, str, parent) {
  229. const div = newElem("div", {"class": "vpp_generic"}, null, parent);
  230. newElem("span", {"id": "vpp_pref_delay_text"}, str, div);
  231. const num = newElem("span", {"id": "vpp_pref_delay_num"}, get_pref("floatDelay").toString() + "s", div);
  232. const minus = newElem("span", {"id": "vpp_pref_delay_minus", "class": "vpp_pref_delay_plusminus"}, "\u2212", div);
  233. const plus = newElem("span", {"id": "vpp_pref_delay_plus", "class": "vpp_pref_delay_plusminus"}, "\u002B", div);
  234.  
  235. minus.onclick = function () {
  236. let val = get_pref("floatDelay");
  237. if (val > 0) {
  238. val--;
  239. set_pref("floatDelay", val);
  240. num.textContent = get_pref("floatDelay").toString() + "s";
  241. }
  242. }
  243.  
  244. plus.onclick = function () {
  245. let val = get_pref("floatDelay");
  246. if (val < 5) {
  247. val++;
  248. set_pref("floatDelay", val);
  249. num.textContent = get_pref("floatDelay").toString() + "s";
  250. }
  251. }
  252.  
  253. }
  254.  
  255.  
  256. function new_checkbox(prefname, str, kind, parent, value, func = function(){}) {
  257. const div = newElem(kind, {"class": "vpp_generic"}, null, parent);
  258. const input = newElem("input", {"class": "vpp_generic", "type": "checkbox"}, null, div);
  259. if (!value) {
  260. input.checked = get_pref(prefname);
  261. input.onclick = function (e) {
  262. const val = get_pref(prefname);
  263. set_pref(prefname, !val);
  264. e.target.checked = !val;
  265. func();
  266. };
  267. }
  268. else {
  269. input.value = value;
  270. input.checked = (get_pref(prefname) == input.value);
  271. input.onclick = function (e) {
  272. const val = get_pref(prefname);
  273. set_pref(prefname, e.target.value);
  274. e.target.checked = true;
  275. const other = innersearch(parent.parentNode, `.//input[@value='${val}']`).snapshotItem(0);
  276. if (other && (other != e.target)) other.checked = false;
  277. func();
  278. };
  279. }
  280. newElem("span", {"class": "vpp_opendelay"}, str, div);
  281. }
  282.  
  283.  
  284. function pref_popup_close() {
  285. const popup = document.getElementById("vpp_pref_popup");
  286. popup?.parentNode?.removeChild(popup);
  287. }
  288.  
  289. function pref_popup_open() {
  290. if (document.getElementById("vpp_pref_popup")) {
  291. return;
  292. }
  293. const popup = newElem("span", {"id": "vpp_pref_popup"}, null, document.body);
  294. newElem("div", {"id": "vpp_pref_title"}, "Preview Options", popup);
  295.  
  296. let changed = false;
  297.  
  298. const closemark = newElem("span", {"id": "vpp_pref_close", "title": "close options"}, "\u2716", popup);
  299. closemark.onclick = function () {
  300. popup.parentNode.removeChild(popup);
  301. if (changed) {
  302. location.reload();
  303. }
  304. };
  305.  
  306. let mark = function(){changed = true;};
  307. new_checkbox("floatEnable", "Hover Preview", "div", popup, null, mark);
  308. new_plusminus("floatDelay", "open delay", popup);
  309. new_checkbox("playerEnable", "Click Preview", "div", popup, null, mark);
  310. new_checkbox("rateEnable", "Video Rating", "div", popup, null, mark);
  311. new_checkbox("defEnable", "Video Resolution", "div", popup, null, mark);
  312. }
  313.  
  314.  
  315. //==================================================================
  316. // Player
  317.  
  318. const basic_str = "((local-name()='ytd-thumbnail') or (local-name()='ytd-playlist-thumbnail')) and (not(ancestor::*[@hidden]))";
  319. const basic_str2 = "//img[contains(@src,'vi/') or contains(@src,'vi_webp/') or contains(@src,'/p/') or contains(@src,'/s_p/')]";
  320.  
  321. function player_script() {
  322. injectScript(`if (YT) {
  323. var player = new YT.Player('vpp_player_frame');
  324. var errort = null;
  325. function error_reset() { if (errort) clearTimeout(errort); }
  326. function check_error(t,fid) {
  327. error_reset();
  328. errort = setTimeout(
  329. function (fid) {
  330. var f = document.getElementById('vpp_player_frame');
  331. if (!f) return;
  332. if (f.getAttribute('fid') != fid) return;
  333. fdoc = f.contentWindow.document;
  334. var s = player.getPlayerState();
  335. var a = player.getPlaylist();
  336. var i = player.getPlaylistIndex();
  337. if (a != null ? a.length == 0 : false)
  338. f.dispatchEvent(new Event('playend'));
  339. else
  340. if ((s == -1 && fdoc.getElementsByClassName('ytp-error').length > 0) || s == 5)
  341. if ((a != null && i != null) ? i < a.length - 1 : false)
  342. player.nextVideo();
  343. else
  344. f.dispatchEvent(new Event('playend'));
  345. }, t, fid);
  346. }
  347. player.addEventListener('onReady',
  348. function () {
  349. var f = document.getElementById('vpp_player_frame');
  350. if (f) {
  351. var q = f.getAttribute('quality');
  352. if (q != 'default' && q != null) player.setPlaybackQualityRange(q);
  353. }
  354. });
  355. player.addEventListener('onStateChange',
  356. function () {
  357. var f = document.getElementById('vpp_player_frame');
  358. if (!f) return;
  359. var fid = f.getAttribute('fid');
  360. var q = f.getAttribute('quality');
  361. var s = player.getPlayerState();
  362. var a = player.getPlaylist();
  363. var i = player.getPlaylistIndex();
  364. var cond = ((a != null && i != null) ? i == a.length - 1 : true);
  365. if (s == -1 || s == 5) check_error(10000,fid); else error_reset();
  366. if (s == -1 && q != 'default' && q != null) player.setPlaybackQualityRange(q);
  367. if (s == 0 && cond) f.dispatchEvent(new Event('playend'));
  368. });
  369. var frame = document.getElementById('vpp_player_frame');
  370. if (frame) {
  371. check_error(10000,frame.getAttribute('fid'));
  372. frame.addEventListener('loadnewvideo',
  373. function (x) {
  374. var fid = x.target.getAttribute('fid');
  375. var url = x.target.getAttribute('newvidurl');
  376. var plist = x.target.getAttribute('plist');
  377. player.pauseVideo();
  378. if (plist) {
  379. player.loadPlaylist({'list':plist});
  380. check_error(10000,fid);
  381. }
  382. else if (url) {
  383. player.loadVideoByUrl(url);
  384. check_error(10000,fid);
  385. }
  386. });
  387. }
  388. }`);
  389. }
  390.  
  391. const choices_def = ['default', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'highres'];
  392. const choices_size = ['xxsmall', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge'];
  393. const choices_pos = ['00', '01', '02', '10', '11', '12', '20', '21', '22'];
  394.  
  395. const scriptPrefs = new Proxy(JSON.parse(GM_getValue("YTVPR_prefs",
  396. `{
  397. "floatEnable": true,
  398. "floatDelay": 1,
  399. "playerEnable": true,
  400. "rateEnable": true,
  401. "defEnable": true,
  402. "playerFit": false,
  403. "playerDef": 0,
  404. "playerSize": 3,
  405. "playerPosLeft": 1,
  406. "playerPosTop": 1,
  407. "playerNext": true,
  408. "playerClose": true,
  409. "playerPause": true,
  410. "playerDim": true
  411. }`)),
  412. {
  413. set: function(obj, prop, value) {
  414. const clamp = function(val, min, max){ return Math.min(Math.max(min, val), max); };
  415. switch (prop) {
  416. case "playerDef": value = clamp(value, 0, choices_def.length()-1); break;
  417. case "playerSize": value = clamp(value, 0, choices_size.length()-1); break;
  418. case "playerPosLeft":
  419. case "playerPosTop": value = clamp(value, 0, 2); break;
  420. }
  421.  
  422. if(obj[prop] != value) {
  423. obj[prop] = value;
  424. GM_setValue("YTVPR_prefs", JSON.stringify(obj));
  425. }
  426. return true;
  427. }
  428. });
  429.  
  430. //player preferences
  431. if (pref_playerEnable) {
  432. init_pref("playerFit", false);
  433. init_pref("playerDef", "default");
  434. init_pref("playerSize", "medium");
  435. init_pref("playerPos", "11");
  436. init_pref("playerNext", true);
  437. init_pref("playerClose", true);
  438. init_pref("playerPause", true);
  439. init_pref("playerDim", true);
  440.  
  441. //fix char preferences
  442. if (choices_def.indexOf(get_pref("playerDef")) < 0) set_pref("playerDef", 'default');
  443. if (choices_size.indexOf(get_pref("playerSize")) < 0) set_pref("playerSize", 'medium');
  444. if (choices_pos.indexOf(get_pref("playerPos")) < 0) set_pref("playerPos", '11');
  445. }
  446.  
  447.  
  448. function player_options(parent) {
  449. if (document.getElementById("vpp_player_options_popup")) {
  450. return;
  451. }
  452.  
  453. const popup = newElem("span", {"id": "vpp_player_options_popup"}, null, parent);
  454. newElem("div", {"class": "vpp_player_options_title"}, "Player Options", popup);
  455.  
  456. const closemark = newElem("span", {"class": "vpp_player_options_close", "title": "close options"}, "\u2716", popup);
  457. closemark.onclick = close_player_options;
  458.  
  459. new_checkbox("playerNext", "Auto Play Next", "div", popup);
  460. new_checkbox("playerDim", "Dim Background", "div", popup, null, function () {
  461. document.getElementById(AREA_ID).style.visibility = (get_pref("playerDim") ? "visible" : "hidden");
  462. });
  463. new_checkbox("playerClose", "Close by Clicking Outside Player", "div", popup);
  464. new_checkbox("playerPause", "Pause YouTube Player at Launch", "div", popup);
  465.  
  466. newElem("div", {"class": "vpp_player_options_text"}, "Resolution", popup);
  467. //default, small, medium, large, hd720, hd1080, hd1440, highres;
  468. const group1 = newElem("div", {"class": "vpp_player_options_group"}, null, popup);
  469. const group2 = newElem("div", {"class": "vpp_player_options_group"}, null, popup);
  470. new_checkbox("playerDef", "Default", "span", group1, "default");
  471. new_checkbox("playerDef", "LQ 240", "span", group1, "small");
  472. new_checkbox("playerDef", "MQ 360", "span", group1, "medium");
  473. new_checkbox("playerDef", "HQ 480", "span", group1, "large");
  474. new_checkbox("playerDef", "HD 720", "span", group2, "hd720");
  475. new_checkbox("playerDef", "HD 1080", "span", group2, "hd1080");
  476. new_checkbox("playerDef", "HD 1440", "span", group2, "hd1440");
  477. new_checkbox("playerDef", "MAX", "span", group2, "highres");
  478. }
  479.  
  480. function close_player_options() {
  481. const popup = document.getElementById("vpp_player_options_popup");
  482. if (popup) {
  483. popup.parentNode.removeChild(popup);
  484. }
  485. }
  486.  
  487.  
  488. function playerUrl(vid, pid) {
  489. let url = `${location.protocol}//${location.hostname}/`;
  490.  
  491. if (vid) {
  492. url = `${url}embed/${vid}?`;
  493. if (pid) {
  494. url = `${url}list=${pid}`;
  495. }
  496. }
  497. else if (pid) {
  498. url = `${url}embed?listType=playlist&list=${pid}`;
  499. }
  500. else {
  501. console.error("Null pid and vid!");
  502. }
  503.  
  504. url = `${url}&autoplay=1&fs=1&iv_load_policy=3&rel=1&version=3&enablejsapi=1`;
  505.  
  506. return (url);
  507. }
  508.  
  509.  
  510. function adjust_playing(node) {
  511. const playing = document.getElementById("vpp_now_playing");
  512. playing?.parentNode?.removeChild(playing);
  513. if (node) {
  514. newElem("span", {"id": "vpp_now_playing"}, "now playing", node);
  515. }
  516. }
  517.  
  518. function build_player() {
  519. //constants
  520. var next_choice = new Object();
  521. next_choice['plus'] = new Object();
  522. next_choice['minus'] = new Object();
  523. next_choice['left'] = new Object();
  524. next_choice['right'] = new Object();
  525. next_choice['up'] = new Object();
  526. next_choice['down'] = new Object();
  527.  
  528. next_choice['plus']['xxsmall'] = 'xsmall';
  529. next_choice['plus']['xsmall'] = 'small';
  530. next_choice['plus']['small'] = 'medium';
  531. next_choice['plus']['medium'] = 'large';
  532. next_choice['plus']['large'] = 'xlarge';
  533. next_choice['plus']['xlarge'] = 'xxlarge';
  534. next_choice['plus']['xxlarge'] = 'xxlarge';
  535.  
  536. next_choice['minus']['xxsmall'] = 'xxsmall';
  537. next_choice['minus']['xsmall'] = 'xxsmall';
  538. next_choice['minus']['small'] = 'xsmall';
  539. next_choice['minus']['medium'] = 'small';
  540. next_choice['minus']['large'] = 'medium';
  541. next_choice['minus']['xlarge'] = 'large';
  542. next_choice['minus']['xxlarge'] = 'xlarge';
  543.  
  544. {
  545. for (var i = 0; i < 3; i++)
  546. for (var j = 0; j < 3; j++) {
  547. next_choice['left'][i.toString() + j.toString()] = i.toString() + (j - 1 >= 0 ? j - 1 : 0).toString();
  548. next_choice['right'][i.toString() + j.toString()] = i.toString() + (j + 1 <= 2 ? j + 1 : 2).toString();
  549. next_choice['up'][i.toString() + j.toString()] = (i - 1 >= 0 ? i - 1 : 0).toString() + j.toString();
  550. next_choice['down'][i.toString() + j.toString()] = (i + 1 <= 2 ? i + 1 : 2).toString() + j.toString();
  551. }
  552. }
  553.  
  554. var new_size = get_pref("playerSize");
  555. var new_pos = get_pref("playerPos");
  556. var new_fit = get_pref("playerFit");
  557.  
  558. var that = this;
  559. var frame_count = 0;
  560.  
  561. //public
  562. this.playerShow = function (vid, pid, node) {
  563. const box = document.getElementById(BOX_ID);
  564. if (box?.style.visibility == "hidden") {
  565. new_size = get_pref("playerSize");
  566. new_pos = get_pref("playerPos");
  567. new_fit = get_pref("playerFit");
  568. }
  569. playerAdjust((new_fit ? "fit" : new_size), new_pos, "visible", vid, pid);
  570. adjust_playing(node);
  571. }
  572.  
  573. this.playerClose = function () {
  574. playerAdjust(null, null, "hidden", null, null);
  575. close_player_options();
  576. adjust_playing();
  577. }
  578.  
  579.  
  580. //private
  581. function play_next(findprevious) {
  582. const playing = document.getElementById("vpp_now_playing");
  583. if (!playing) {
  584. return;
  585. }
  586.  
  587. const myimg = innersearch(playing.parentNode, ".//img[@src or @data-thumb]").snapshotItem(0);
  588. if (myimg) {
  589. let pos = -2;
  590. let l = docsearch(`//*[(${basic_str})]${basic_str2}`);
  591.  
  592. myimg.setAttribute("matchfind", "true");
  593.  
  594. for (let i = 0; i < l.snapshotLength; i++) {
  595. if (l.snapshotItem(i).getAttribute("matchfind")) {
  596. pos = i;
  597. break;
  598. }
  599. }
  600.  
  601. myimg.removeAttribute("matchfind");
  602. pos = (findprevious ? pos - 1 : pos + 1);
  603.  
  604. if (pos >= 0) {
  605. const img = l.snapshotItem(pos);
  606. if (img) {
  607. img.setAttribute("matchfindimg", true);
  608. const target = docsearch(`//*[${basic_str} and (.//img[@matchfindimg])]`).snapshotItem(0);
  609. if (target) {
  610. that.playerShow(find_vid(img), find_plist(img), target);
  611. }
  612. img.removeAttribute("matchfindimg");
  613. }
  614. }
  615. }
  616. }
  617.  
  618. function playerAdjust(size, pos, vis, vid, pid) {
  619. const box = document.getElementById(BOX_ID);
  620. if (vis != null) {
  621. box.style.visibility = vis;
  622. area.style.visibility = (get_pref("playerDim") ? vis : "hidden");
  623.  
  624. let frame = document.getElementById("vpp_player_frame");
  625. if (frame && (vis == "hidden")) {
  626. frame.parentNode.removeChild(frame);
  627. frame = null;
  628. }
  629.  
  630. if (vis == "visible") {
  631. const vidurl = playerUrl(vid, pid);
  632. const def = get_pref("playerDef");
  633. frame_count++;
  634.  
  635. if (frame) {
  636. frame.setAttribute("newvidurl", vidurl);
  637. if (pid) frame.setAttribute("plist", pid);
  638. else frame.removeAttribute("plist");
  639. frame.setAttribute("quality", def);
  640. frame.setAttribute("fid", frame_count.toString());
  641.  
  642. const event = document.createEvent("Event");
  643. event.initEvent("loadnewvideo", true, true);
  644. frame.dispatchEvent(event);
  645. }
  646. else {
  647. frame = newElem("iframe", {"id": "vpp_player_frame", "type": "text/html", "frameborder": "0", "allowfullscreen": "true", "quality": def, "fid": frame_count.toString(), "src": vidurl}, null, document.getElementById(HOLDER_ID).firstElementChild);
  648. frame.addEventListener("playend", function(){if (get_pref("playerNext")) play_next();});
  649. player_script();
  650. }
  651. }
  652. }
  653.  
  654. if (size != null) {
  655. box.setAttribute("player_size", size);
  656. holder.setAttribute("player_size", size);
  657. }
  658.  
  659. if (pos != null) {
  660. box.setAttribute("player_pos", pos);
  661. }
  662. }
  663.  
  664. function click_pos_size(e) {
  665. var kind = e.target.getAttribute("button_kind");
  666. if (!kind) return;
  667.  
  668. if (new_fit && !(kind == "options" || kind == "prev" || kind == "next")) {
  669. set_pref("playerFit", false);
  670. new_fit = false;
  671. playerAdjust(new_size, new_pos);
  672. return;
  673. }
  674.  
  675. switch (kind) {
  676. case 'plus':
  677. case 'minus':
  678. new_size = next_choice[kind][new_size];
  679. set_pref("playerSize", new_size);
  680. playerAdjust(new_size, new_pos);
  681. break;
  682.  
  683. case 'fit':
  684. set_pref("playerFit", true);
  685. new_fit = true;
  686. playerAdjust('fit');
  687. break;
  688.  
  689. case 'left':
  690. case 'right':
  691. case 'up':
  692. case 'down':
  693. new_pos = next_choice[kind][new_pos];
  694. set_pref("playerPos", new_pos);
  695. playerAdjust(new_size, new_pos);
  696. break;
  697.  
  698. case 'options':
  699. player_options(document.getElementById(BOX_ID));
  700. break;
  701.  
  702. case 'prev':
  703. play_next(true);
  704. break;
  705.  
  706. case 'next':
  707. play_next();
  708. break;
  709. }
  710. }
  711.  
  712. function new_button(kind, str, str_popup, parent) {
  713. newElem("span", {"class": "vpp_player_button", "title": str_popup, "button_kind": kind}, str, parent).onclick = click_pos_size;
  714. }
  715.  
  716. //initialization;
  717. if (document.getElementById(AREA_ID)) {
  718. return;
  719. }
  720.  
  721. const area = newElem("div", {"id": AREA_ID, "style": "visibility: hidden;"}, null, document.body);
  722. const area2 = newElem("div", {"id": "vpp_player_area2"}, null, area);
  723. const box = newElem("div", {"id": BOX_ID, "style": "visibility: hidden;"}, null, area2);
  724. const holder = newElem("div", {"id": HOLDER_ID}, null, box);
  725. newElem("div", {"id": "vpp_player_holder2"}, null, holder);
  726. area.onclick = function (e) { if (e.target.id == AREA_ID && get_pref("playerClose")) that.playerClose(); };
  727.  
  728. const buttonArea = newElem("span", {"id": "vpp_player_button_area_top"}, null, box);
  729. new_button("plus", "\u002B", "increase size", buttonArea);
  730. new_button("minus", "\u2212", "decrease size", buttonArea);
  731. new_button("fit", "\u2610", "fill window", buttonArea);
  732. new_button('left', '\u25C4', 'move left', buttonArea);
  733. new_button('right', '\u25BA', 'move right', buttonArea);
  734. new_button('up', '\u25B2', 'move up', buttonArea);
  735. new_button('down', '\u25BC', 'move down', buttonArea);
  736. new_button("options", "\u2630", "player options", buttonArea);
  737.  
  738. const bottomArea = newElem("span", {"id": "vpp_player_button_area_next"}, null, box);
  739. new_button("prev", "\u140A\u140A", "play previous in page", bottomArea);
  740. new_button("next", "\u1405\u1405", "play next in page", bottomArea);
  741. //new_button("loop", "\u21BB", "repeat video", bottomArea);
  742.  
  743. newElem("span", {"id": "vpp_player_close_mark", "title": "close player"}, "\u2716", box).onclick = this.playerClose;
  744. }
  745.  
  746. var player = null;
  747. if (pref_playerEnable) {
  748. player = new build_player();
  749. injectScript(null, "https://www.youtube.com/iframe_api");
  750. }
  751.  
  752. function ytpause() {
  753. const func = `(function(){
  754. const mainVid = document.getElementById("movie_player");
  755. const channelVid = document.getElementById("c4-player");
  756. if (mainVid && (mainVid.getPlayerState() == 1))
  757. mainVid.pauseVideo();
  758. if (channelVid && (channelVid.getPlayerState() == 1))
  759. channelVid.pauseVideo();
  760. })();`;
  761. injectScript(func);
  762. }
  763.  
  764.  
  765. //==================================================================
  766. // float player
  767.  
  768. //quality is default for faster upload, and youtube player is paused
  769. function float_script() {
  770. const func = `(function(){
  771. const fplayer = new YT.Player("vpp_float_frame");
  772. fplayer.addEventListener("onReady", function () {
  773. const mainVid = document.getElementById("movie_player");
  774. const channelVid = document.getElementById("c4-player");
  775. if (mainVid && (mainVid.getPlayerState() == 1))
  776. mainVid.pauseVideo();
  777. if (channelVid && (channelVid.getPlayerState() == 1))
  778. channelVid.pauseVideo();
  779. fplayer.setPlaybackQualityRange("default");
  780. });
  781. })();`;
  782. injectScript(func);
  783. }
  784.  
  785. function float_open(e, check) {
  786. //check tests if same url frame is already open
  787. const v_id = e.target.firstElementChild?.href?.match(/(?<=v=)[a-zA-Z0-9_-]*/)?.[0];
  788.  
  789. if (!v_id || document.getElementById("vpp_player_frame")) {
  790. return false;
  791. }
  792.  
  793. if (!check) {
  794. float_delay_clear();
  795. adjust_playing(e.target);
  796. }
  797.  
  798. const url = `https://www.youtube.com/embed/${v_id}?&autoplay=1&controls=1&iv_load_policy=3&rel=0&showinfo=1&version=3&enablejsapi=1`;
  799. const frame = document.getElementById("vpp_float_frame");
  800. if (frame) {
  801. if (frame.src == url) {
  802. return check;
  803. }
  804. if (!check) {
  805. frame.parentNode.removeChild(frame);
  806. }
  807. }
  808.  
  809. if (check) {
  810. return false;
  811. }
  812.  
  813. const float_width = 480; //512;
  814. const float_height = 270; //288;
  815. let box = document.getElementById("vpp_float_box");
  816. if (!box) {
  817. box = newElem("div", {"id": "vpp_float_box", "style": `width: ${float_width}px; height: ${float_height}px;`}, null, document.body);
  818. box.onmouseenter = float_delay_clear;
  819. box.onmouseleave = float_close_delay;
  820. }
  821.  
  822. const r = e.target.getBoundingClientRect();
  823. const w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  824. const h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
  825. const hpad = Math.round(-r.width / 3); //horizontal offset
  826. const vpad = 0; //vertical offset
  827.  
  828. //priority to right
  829. let left = r.right + hpad;
  830. if ((left + float_width > w) && ((r.left - float_width - hpad >= 0) || (r.left > w - r.right))) {
  831. left = r.left - float_width - hpad;
  832. }
  833.  
  834. //priority to left
  835. //let left = r.left - float_width - pad;
  836. //if (left < 0)
  837. // if ((r.right + float_width + pad <= w) || (w - r.right > r.left))
  838. // left = r.right + pad;
  839.  
  840. //priority to top
  841. let top = r.top - float_height - vpad;
  842. if (top < 0 && (r.bottom + float_height + vpad <= h || h - r.bottom > r.top)) {
  843. top = r.bottom + vpad;
  844. }
  845.  
  846. left += (document.body.scrollLeft || document.documentElement.scrollLeft);
  847. top += (document.body.scrollTop || document.documentElement.scrollTop);
  848.  
  849. box.style.left = left + "px";
  850. box.style.top = top + "px";
  851.  
  852. newElem("iframe", {"id": "vpp_float_frame", "type": "text/html", "frameborder": "0", "src": url}, null, box);
  853.  
  854. float_script();
  855. return true;
  856. }
  857.  
  858. let float_open_timeout = null;
  859. let float_close_timeout = null;
  860.  
  861. function float_close(e) {
  862. clearTimeout(float_close_timeout);
  863. const box = document.getElementById("vpp_float_box");
  864.  
  865. //check if mouse was in the area
  866. if (box) {
  867. if (!e || (e.target != box)) {
  868. box.parentNode.removeChild(box);
  869. adjust_playing();
  870. }
  871. else { //player should not close if mouse is still inside
  872. const r = box.getBoundingClientRect();
  873. if ((e.clientX <= r.left + 1) || (e.clientX >= r.right - 1) ||
  874. (e.clientY <= r.top + 1) || (e.clientY >= r.bottom - 1)) {
  875. box.parentNode.removeChild(box);
  876. adjust_playing();
  877. }
  878. }
  879. }
  880. }
  881.  
  882.  
  883. function float_delay_clear() {
  884. clearTimeout(float_open_timeout);
  885. clearTimeout(float_close_timeout);
  886. }
  887.  
  888. function float_reset() {
  889. float_delay_clear();
  890. float_close();
  891. }
  892.  
  893. function float_open_delay(e) {
  894. if (float_open(e, true)) {
  895. float_delay_clear();
  896. }
  897. else {
  898. clearTimeout(float_open_timeout);
  899. const delay = get_pref("floatDelay") * 1000;
  900. float_open_timeout = setTimeout(float_open.bind(null, e, false), delay);
  901. }
  902. }
  903.  
  904. function float_close_delay(e) {
  905. float_delay_clear();
  906. float_close_timeout = setTimeout(float_close.bind(null, e), 200);
  907. }
  908.  
  909.  
  910. //==================================================================
  911. //meta data
  912.  
  913. function innertube_callback(data, parent) {
  914. let max_res = 0;
  915. let max_quality_label = "";
  916. let formats = [];
  917.  
  918. if (data.streamingData?.hasOwnProperty("formats")) {
  919. formats = formats.concat(data.streamingData.formats);
  920. }
  921. if (data.streamingData?.hasOwnProperty("adaptiveFormats")) {
  922. formats = formats.concat(data.streamingData.adaptiveFormats);
  923. }
  924. for (let i = 0; i < formats.length; i++) {
  925. const res = formats[i].width * formats[i].height;
  926. if (res > max_res) {
  927. max_res = res;
  928. max_quality_label = formats[i].qualityLabel;
  929. }
  930. }
  931. if(max_res != 0) {
  932. parent.setAttribute("vpp_meta_def", "");
  933. const def_node = newElem("span", {"class": "vpp_meta_def_container", "reveal": "true", "title": max_quality_label}, null, parent);
  934. const def_txt =
  935. max_res >= (15360 * 8640) ? "16K" :
  936. max_res >= (7680 * 4320) ? "8K" :
  937. max_res >= (2880 * 2160) ? "4K" :
  938. max_res >= (960 * 1080) ? "1080p" :
  939. max_res >= (640 * 720) ? "720p" : "SD";
  940. newElem("span", {"class": "vpp_meta_def_hd HD"}, def_txt, def_node);
  941. }
  942. }
  943.  
  944. function rytdl_callback(data, parent) {
  945. if (data.likes > 0 || data.dislikes > 0) {
  946. const perc = Math.round((data.rating/5)*100);
  947. // Color scale from 50 (red) to 100 (green)
  948. const scaled = Math.max(0,(perc-50)/50);
  949. const r = Math.min(0xC0,Math.round(2*0xC0*(1-scaled)));
  950. const g = Math.min(0xA0,Math.round(2*0xA0*(scaled)));
  951. const b = 0;
  952. const rgb = (r << 16) | (g << 8) | b;
  953. const hex = `#${rgb.toString(16).padStart(6, "0")}`;
  954.  
  955. newElem("div",
  956. { "class": "vpp_meta_rate",
  957. "style": `background:${hex} !important;`,
  958. "title": `${Number(data.viewCount).toLocaleString()} views`
  959. },
  960. `${perc}`,
  961. parent);
  962. parent.setAttribute("vpp_meta_rate", "");
  963. }
  964. }
  965.  
  966. var innertube_key, client_version;
  967. const scripts = document.getElementsByTagName("script");
  968. for (let script of scripts) {
  969. innertube_key = script.textContent?.match(/"INNERTUBE_API_KEY":"([^"]*?)"/)?.[1];
  970. if (innertube_key) {
  971. client_version = script.textContent?.match(/"INNERTUBE_CLIENT_VERSION":"([^"]*?)"/)?.[1];
  972. break;
  973. }
  974. }
  975.  
  976. const rytdl_queue = new Map();
  977. const rytdl_counter = [];
  978. function rytdl_req() {
  979. const v_id = rytdl_queue.keys().next().value;
  980. const rytdlReq = new XMLHttpRequest();
  981. rytdlReq.responseType = "json";
  982. rytdlReq.addEventListener("loadend", function() {
  983. if (this.response) {
  984. rytdl_callback(this.response, rytdl_queue.get(v_id));
  985. } else {
  986. console.error("rytdl request failed");
  987. }
  988. rytdl_queue.delete(v_id);
  989. if (rytdl_queue.size > 0) {
  990. let timeout = 600; // 100 reqs/min
  991. /* Needs Timing-Allow-Origin from server for this to work
  992. if (this.response) {
  993. const resources = performance.getEntriesByType("resource");
  994. for (let entry of resources) {
  995. if (entry.name === this.responseURL && entry.transferSize === 0) {
  996. timeout = 0;
  997. break;
  998. }
  999. }
  1000. } */
  1001. setTimeout(rytdl_req, timeout);
  1002. }
  1003. });
  1004. rytdlReq.open("GET", `https://returnyoutubedislikeapi.com/Votes?videoId=${v_id}`);
  1005. rytdlReq.send();
  1006. }
  1007.  
  1008. function def_rate(v_id, parent) {
  1009. debug(`Getting info for ${v_id}`);
  1010. if (pref_defEnable) {
  1011. const params = {
  1012. "context": {
  1013. "client": {
  1014. "hl": "en",
  1015. "clientName": "WEB",
  1016. "clientVersion": `${client_version}`,
  1017. "clientFormFactor": "UNKNOWN_FORM_FACTOR",
  1018. "clientScreen": "WATCH",
  1019. "mainAppWebInfo": {
  1020. "graftUrl": `/watch?v=${v_id}`,
  1021. }
  1022. },
  1023. "user": {
  1024. "lockedSafetyMode": false
  1025. },
  1026. "request": {
  1027. "useSsl": true,
  1028. "internalExperimentFlags": [],
  1029. "consistencyTokenJars": []
  1030. }
  1031. },
  1032. "videoId": `${v_id}`,
  1033. "playbackContext": {
  1034. "contentPlaybackContext": {
  1035. "vis": 0,
  1036. "splay": false,
  1037. "autoCaptionsDefaultOn": false,
  1038. "autonavState": "STATE_NONE",
  1039. "html5Preference": "HTML5_PREF_WANTS",
  1040. "lactMilliseconds": "-1"
  1041. }
  1042. },
  1043. "racyCheckOk": false,
  1044. "contentCheckOk": false
  1045. };
  1046.  
  1047. const innertubeReq = new XMLHttpRequest();
  1048. innertubeReq.responseType = "json";
  1049. innertubeReq.addEventListener("loadend", function(){
  1050. if (this.response) {
  1051. innertube_callback(this.response, parent);
  1052. } else {
  1053. console.error("innertube request failed");
  1054. }
  1055. });
  1056. // https://stackoverflow.com/questions/67615278/get-video-info-youtube-endpoint-suddenly-returning-404-not-found
  1057. innertubeReq.open("POST", `https://www.youtube.com/youtubei/v1/player?key=${innertube_key}`);
  1058. innertubeReq.send(JSON.stringify(params));
  1059. }
  1060.  
  1061. if (pref_rateEnable) {
  1062. rytdl_queue.set(v_id, parent);
  1063. if (rytdl_queue.size === 1) {
  1064. rytdl_req();
  1065. }
  1066. }
  1067. }
  1068.  
  1069.  
  1070. function find_vid(img) {
  1071. return (filter(img.src, "vi/", "/&?#") || filter(img.src, "vi_webp/", "/&?#"));
  1072. }
  1073.  
  1074.  
  1075. function find_plist(img) {
  1076. let plist = null;
  1077. const anc = innersearch(img, "ancestor-or-self::*[contains(@href,'&list=') and (.//*[contains(@class,'yt-pl-sidebar-content') or contains(@class,'ytd-thumbnail-overlay-side-panel-renderer')])]").snapshotItem(0);
  1078. if (anc) {
  1079. plist = filter(anc.href, "list=", "/&?#");
  1080. if (plist == "WL") {
  1081. plist = null;
  1082. }
  1083. }
  1084. return plist;
  1085. }
  1086.  
  1087.  
  1088. function play(parent) {
  1089. const playArea = newElem("div", {"class": "vpp_play_button_container"}, null, parent);
  1090. const playNode = newElem("a", {"class": "vpp_play_button", "href": "javascript:;", "target": "_self", "title": "click to preview"}, null, playArea);
  1091.  
  1092. const play_handle = function(e) {
  1093. e.stopPropagation();
  1094. float_reset();
  1095.  
  1096. const parpar = e.target.parentNode.parentNode;
  1097. if (innersearch(parpar, ".//*[@id='vpp_now_playing']").snapshotLength > 0) {
  1098. player.playerClose();
  1099. }
  1100. else {
  1101. const img = innersearch(parpar, `.${basic_str2}`).snapshotItem(0);
  1102. if (img) {
  1103. player.playerShow(find_vid(img), find_plist(img), parpar);
  1104. if (get_pref("playerPause")) {
  1105. ytpause();
  1106. }
  1107. }
  1108. else {
  1109. console.error("play(parent): img not found");
  1110. }
  1111. }
  1112. }
  1113.  
  1114. playNode.onclick = play_handle;
  1115. }
  1116.  
  1117.  
  1118. //==================================================================
  1119. // Main
  1120. debug("***YouTube Video Preview and Ratings Keyless***");
  1121.  
  1122. //insert styles
  1123. insertStyle(style_basic, "vpp_style_basic");
  1124.  
  1125. if (pref_playerEnable) {//hide overlay of playlist
  1126. insertStyle(".yt-pl-thumb-overlay, ytd-thumbnail-overlay-hover-text-renderer {display:none !important;}", "vpp_style_list_overlay");//OLD,NEW
  1127. }
  1128.  
  1129. (function insertMenuBtn() {
  1130. const par = document.getElementById("masthead-container");
  1131. if (par) {
  1132. newElem("span", {"id": "vpp_pref_button", "title": "Preview Options"}, null, par).onclick = pref_popup_open;
  1133. }
  1134. else {
  1135. new MutationObserver(function(mutations, observer) {
  1136. observer.disconnect();
  1137. insertMenuBtn();
  1138. }).observe(document.getElementById("masthead"), {childList: true});
  1139. }
  1140. })();
  1141.  
  1142. function processThumbs(thumbs) {
  1143. for (let thumb of thumbs) {
  1144. const parent = thumb.parentNode;
  1145. const v_id = thumb.href?.match(/(?<=v=|shorts\/)[a-zA-Z0-9_-]*/)?.[0];
  1146. if (v_id) {
  1147. if (pref_defEnable || pref_rateEnable) {
  1148. def_rate(v_id, parent);
  1149. }
  1150. if (pref_floatEnable) {
  1151. parent.onmouseenter = float_open_delay;
  1152. parent.onmouseleave = float_close_delay;
  1153. }
  1154. if (pref_playerEnable) {
  1155. play(parent);
  1156. }
  1157. }
  1158. }
  1159. }
  1160. processThumbs(Array.from(document.body.querySelectorAll("#thumbnail")));
  1161.  
  1162. new MutationObserver(function(mutations, observer) {
  1163. for (let mutation of mutations) {
  1164. const thumbs = Array.from(mutation.addedNodes).filter(node => (node.nodeType === Node.ELEMENT_NODE && node.id === "thumbnail"));
  1165. processThumbs(thumbs);
  1166. }
  1167. }).observe(document.body, {childList: true, subtree: true});