Pornhub Pro-ish

Alters and improves the PH experience, see addition info

  1. // ==UserScript==
  2. // @name Pornhub Pro-ish
  3. // @namespace https://www.reddit.com/user/Alpacinator
  4. // @version 3.5.7
  5. // @include *://*.pornhub.com/*
  6. // @grant none
  7. // @description Alters and improves the PH experience, see addition info
  8. // ==/UserScript==
  9.  
  10. (function() {
  11. 'use strict';
  12.  
  13. // central logging function
  14. function log(message) {
  15. console.log(`Pornhub Pro-ish: ${message}`);
  16. }
  17.  
  18. // still gotta find a better place for this
  19. let inputFilterWords;
  20.  
  21. // these are the toggles that are dynamically created.
  22. // Only the changeEvent should be created manually.
  23. // the creation of the locally stored states is done automatically thanks to createToggle & getToggleState
  24. const toggles = [{
  25. label: 'Sort within playlists',
  26. key: 'sortWithinPlaylistsState',
  27. changeEvent: initializeSortWithinPlaylists,
  28. id: 'sortWithinPlaylistsToggle',
  29. defaultState: false
  30. }, {
  31. label: 'Sort videos by 🏆',
  32. key: 'sortByTrophyState',
  33. changeEvent: initializeSortByTrophy,
  34. id: 'sortByTrophyToggle',
  35. defaultState: false
  36. }, {
  37. label: 'Sort videos by duration',
  38. key: 'sortByDurationState',
  39. changeEvent: initializeSortByDuration,
  40. id: 'sortByDurationToggle',
  41. defaultState: false
  42. }, {
  43. label: 'Hide watched videos',
  44. key: 'hideWatchedState',
  45. changeEvent: hideVideos,
  46. id: 'hideWatchedToggle',
  47. defaultState: false
  48. }, {
  49. label: 'Hide paid content',
  50. key: 'hidePaidContentState',
  51. changeEvent: hideVideos,
  52. id: 'hidePaidContentToggle',
  53. defaultState: true
  54. }, {
  55. label: 'Always use English',
  56. key: 'redirectToEnglishState',
  57. changeEvent: redirectToEnglish,
  58. id: 'redirectToEnglishToggle',
  59. defaultState: true
  60. }, {
  61. label: 'Mute by default',
  62. key: 'muteState',
  63. changeEvent: initializeMuteVideos,
  64. id: 'muteToggle',
  65. defaultState: false
  66. }, {
  67. label: 'Hide cursor on video',
  68. key: 'cursorHideState',
  69. changeEvent: initializeCursorHide,
  70. id: 'cursorHideToggle',
  71. defaultState: true
  72. }];
  73.  
  74. function createSideMenu() {
  75. log('Creating menu..');
  76. let menuShowState = getToggleState('menuShowState', true); // Get initial state from localStorage for the menu, default is true(show)
  77. const sideMenu = document.createElement('div');
  78. sideMenu.id = 'sideMenu'; // Assign an ID for easier reference
  79. sideMenu.style.position = 'fixed';
  80. sideMenu.style.top = '0';
  81. sideMenu.style.left = '0';
  82. sideMenu.style.padding = '60px 20px 20px 20px'; // Top padding 40px, rest 20px
  83. sideMenu.style.height = '100%'; // Full height for mobile view
  84. sideMenu.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
  85. sideMenu.style.zIndex = '9999';
  86. sideMenu.style.display = 'flex';
  87. sideMenu.style.flexDirection = 'column'; // Stack items vertically
  88. sideMenu.style.justifyContent = 'center';
  89. sideMenu.style.alignItems = 'left';
  90.  
  91. // Add the toggles dynamically to the side menu
  92. // using const toggles and createToggle
  93. toggles.forEach(({
  94. label,
  95. key,
  96. changeEvent,
  97. id,
  98. defaultState
  99. }) => {
  100. const toggle = createToggle(label, key, changeEvent, id, defaultState);
  101. sideMenu.appendChild(toggle);
  102. });
  103.  
  104.  
  105. // create the two manual buttons
  106. var sortByTrophyManualButton = createButton(
  107. 'Put 🏆 first manually',
  108. 'black',
  109. sortByTrophyButton
  110. );
  111. sideMenu.appendChild(sortByTrophyManualButton);
  112.  
  113. var sortBylengthManualButton = createButton(
  114. 'Sort by duration',
  115. 'black',
  116. sortByDurationButton
  117. );
  118. sideMenu.appendChild(sortBylengthManualButton);
  119.  
  120. // this shows a little label above the filter input for the user
  121. var filterInfo = document.createElement('span');
  122. filterInfo.textContent = 'Words to filter out:';
  123. filterInfo.style.color = 'white';
  124. filterInfo.style.marginTop = '20px';
  125. filterInfo.style.paddingLeft = '20%';
  126. filterInfo.style.lineHeight = '20px'; // Match the height of the toggle for vertical alignment
  127. filterInfo.style.alignItems = 'center';
  128. filterInfo.style.justifyContent = 'center';
  129. filterInfo.style.fontSize = '14px'; // Adjust font size if needed
  130.  
  131. sideMenu.appendChild(filterInfo);
  132.  
  133. // create the text input for the filter
  134. inputFilterWords = createTextInput('inputFilterWords', 'Seperate with space or comma', updateFilterWords);
  135. var savedFilterWords = localStorage.getItem('savedFilterWords');
  136. if (savedFilterWords) {
  137. inputFilterWords.value = savedFilterWords; // Set the value of the input field
  138. }
  139.  
  140. sideMenu.appendChild(inputFilterWords);
  141.  
  142. // Insert the menu into the body
  143. document.body.insertBefore(sideMenu, document.body.firstChild);
  144.  
  145. // Add the expand menu toggle
  146. createMenuToggle();
  147.  
  148. // load the visibility state of the side menu and apply the according styling
  149. syncSideMenu();
  150. }
  151.  
  152. // this block is responsible for hiding and showing the side menu
  153. // creating the toggle, and syncing the state with the saved show/hide menu state
  154. function createMenuToggle() {
  155. const symbol = document.createElement('div');
  156. const menuShowState = getToggleState('menuShowState');
  157. symbol.id = 'menuToggle';
  158. symbol.textContent = menuShowState ? 'Hide Menu' : 'Show Menu';
  159. symbol.style.position = 'fixed';
  160. symbol.style.left = '5px'; // Position on the right
  161. symbol.style.top = '5px';
  162. symbol.style.fontSize = '12pt';
  163.  
  164. symbol.style.color = 'orange';
  165. symbol.style.cursor = 'pointer';
  166.  
  167. symbol.style.zIndex = '10000';
  168. symbol.style.transition = 'all 0.3s';
  169.  
  170. symbol.style.padding = '5px 10px';
  171. symbol.style.backgroundColor = 'black';
  172. symbol.style.border = '1px solid orange';
  173. symbol.style.borderRadius = '15px';
  174.  
  175. symbol.addEventListener('click', toggleMenuShow);
  176. document.body.appendChild(symbol);
  177. }
  178.  
  179. function showMenu() {
  180. const sideMenu = document.getElementById('sideMenu');
  181. if (sideMenu) {
  182. // Show menu
  183. sideMenu.style.visibility = 'visible';
  184. sideMenu.style.transition = 'opacity 0.5s ease, transform 0.5s ease, visibility 0s ease';
  185. sideMenu.style.opacity = '1';
  186. sideMenu.style.transform = 'translateX(0)';
  187.  
  188. const menuToggle = document.getElementById('menuToggle');
  189. menuToggle.textContent = 'Hide Menu';
  190. }
  191. }
  192.  
  193. function hideMenu() {
  194. const sideMenu = document.getElementById('sideMenu');
  195. if (sideMenu) {
  196. // Hide menu
  197. sideMenu.style.visibility = 'hidden';
  198. sideMenu.style.transition = 'opacity 0.5s ease, transform 0.5s ease, visibility 0s ease 0.5s';
  199. sideMenu.style.opacity = '0';
  200. sideMenu.style.transform = 'translateX(-100%)';
  201.  
  202. const menuToggle = document.getElementById('menuToggle');
  203. menuToggle.textContent = 'Show Menu';
  204. }
  205. }
  206.  
  207. function toggleMenuShow() {
  208. const sideMenu = document.getElementById('sideMenu');
  209. let menuShowState = getToggleState('menuShowState');
  210. if (sideMenu) {
  211. if (!menuShowState) {
  212. showMenu();
  213. } else {
  214. hideMenu();
  215. }
  216.  
  217. menuShowState = !menuShowState;
  218. localStorage.setItem('menuShowState', menuShowState);
  219.  
  220. }
  221. }
  222.  
  223. function syncSideMenu() {
  224. let menuShowState = getToggleState('menuShowState');
  225. if (menuShowState) {
  226. showMenu();
  227. } else {
  228. hideMenu();
  229. }
  230. }
  231. // end block for showing/hiding menu
  232.  
  233. // Update the toggle creation to include default states
  234. function createToggle(labelText, localStorageKey, changeEvent, id, defaultState = false) {
  235. var container = document.createElement('div');
  236. container.style.display = 'flex';
  237. container.style.alignItems = 'center';
  238. container.style.marginBottom = '15px'; // Add space between toggle and next element if needed
  239.  
  240. var toggle = document.createElement('div');
  241. toggle.style.position = 'relative';
  242. toggle.style.width = '40px';
  243. toggle.style.height = '20px';
  244. toggle.style.backgroundColor = getToggleState(localStorageKey, defaultState) ? 'orange' : 'grey';
  245. toggle.style.borderRadius = '20px';
  246. toggle.style.cursor = 'pointer';
  247. toggle.style.transition = 'background-color 0.2s';
  248. toggle.id = id;
  249.  
  250. var slider = document.createElement('div');
  251. slider.style.position = 'absolute';
  252. slider.style.left = getToggleState(localStorageKey, defaultState) ? '22px' : '2px';
  253. slider.style.width = '16px';
  254. slider.style.height = '16px';
  255. slider.style.backgroundColor = 'white';
  256. slider.style.borderRadius = '50%';
  257. slider.style.transition = 'left 0.2s';
  258. slider.style.top = '2px'; // Vertically center the slider within the toggle
  259.  
  260. toggle.appendChild(slider);
  261.  
  262.  
  263. // this is used to save the state of the toggle to localStorage automatically
  264. toggle.addEventListener('click', function() {
  265. var currentState = getToggleState(localStorageKey, defaultState);
  266. localStorage.setItem(localStorageKey, !currentState);
  267. toggle.style.backgroundColor = !currentState ? 'orange' : 'grey';
  268. slider.style.left = !currentState ? '22px' : '2px';
  269. changeEvent();
  270. });
  271.  
  272. var span = document.createElement('span');
  273. span.textContent = labelText;
  274. span.style.color = 'white';
  275. span.style.marginLeft = '15px'; // Space between toggle and text
  276. span.style.lineHeight = '20px'; // Match the height of the toggle for vertical alignment
  277. span.style.fontSize = '14px'; // Adjust font size if needed
  278. span.style.paddingTop = '3px'; // Adjust font size if needed
  279.  
  280. container.appendChild(toggle);
  281. container.appendChild(span);
  282.  
  283. return container;
  284. }
  285.  
  286. // this checks the current state of the toggles using the ID and what they should be according to the saved values in LocalStorage
  287. function updateToggleStates() {
  288. toggles.forEach(({
  289. id,
  290. key
  291. }) => {
  292. const toggleElement = document.getElementById(id);
  293. const currentState = getToggleState(key);
  294.  
  295. if (toggleElement) {
  296. const expectedColor = currentState ? 'orange' : 'grey';
  297. const slider = toggleElement.querySelector('div');
  298.  
  299. toggleElement.style.backgroundColor = expectedColor;
  300. if (slider) {
  301. slider.style.left = currentState ? '22px' : '2px';
  302. }
  303. }
  304. });
  305. }
  306.  
  307. function createButton(text, bgColor, clickEvent) {
  308. var button = document.createElement('button');
  309. button.textContent = text;
  310. button.style.marginRight = '0px';
  311. button.style.marginBottom = '15px';
  312. button.style.padding = '5px 10px';
  313. button.style.backgroundColor = bgColor;
  314. button.style.color = 'white';
  315. button.style.border = '1px solid white';
  316. button.style.borderRadius = '10px';
  317. button.style.cursor = 'pointer';
  318. button.style.transition = 'background-color 0.3s, color 0.3s, border-color 0.3s';
  319.  
  320. // JavaScript hover effects
  321. button.addEventListener('mouseover', function() {
  322. button.style.color = 'orange';
  323. button.style.borderColor = 'orange';
  324. });
  325. button.addEventListener('mouseout', function() {
  326. button.style.color = 'white';
  327. button.style.borderColor = 'white';
  328. });
  329.  
  330. button.addEventListener('click', function() {
  331. button.style.backgroundColor = 'orange';
  332. setTimeout(function() {
  333. button.style.backgroundColor = bgColor;
  334. }, 100);
  335.  
  336. clickEvent();
  337. });
  338.  
  339. return button;
  340. }
  341. // dynamically create text inputs function for the menu
  342. function createTextInput(id, placeholder, inputEvent) {
  343. var input = document.createElement('input');
  344. input.type = 'text';
  345. input.id = id;
  346. input.placeholder = placeholder;
  347. input.style.marginRight = '0px';
  348. input.style.marginBottom = '15px';
  349. input.style.border = '1px solid grey';
  350. input.style.borderRadius = '15px';
  351. input.style.padding = '5px 10px';
  352. input.addEventListener('input', inputEvent);
  353. return input;
  354. }
  355.  
  356. // this is used to fetch the state of a setting in LocalStorage
  357. function getToggleState(localStorageKey, defaultState = false) {
  358. const savedState = localStorage.getItem(localStorageKey);
  359. if (savedState === null) { // No saved state exists
  360. localStorage.setItem(localStorageKey, defaultState); // Set the default state in localStorage
  361. return defaultState;
  362. }
  363. return savedState === 'true';
  364. }
  365.  
  366. // manage the filter words to hide certain videos if the title includes certain words
  367. function updateFilterWords() {
  368. var inputFilterWordsValue = inputFilterWords.value;
  369. localStorage.setItem('savedFilterWords', inputFilterWordsValue);
  370. hideVideos();
  371. }
  372.  
  373. // Check whatever the state of the toggles is and execute the functions accordingly
  374. function initializeMuteVideos() {
  375. var muteVideosEnabled = getToggleState('muteState');
  376. if (muteVideosEnabled) {
  377. clickMuteButton();
  378. }
  379. }
  380.  
  381. function initializeSortByTrophy() {
  382. var reorderItemsEnabled = getToggleState('sortByTrophyState');
  383. if (reorderItemsEnabled) {
  384. sortByTrophy();
  385. }
  386. }
  387.  
  388. function initializeSortByDuration() {
  389. var sortByDurationEnabled = getToggleState('sortByDurationState');
  390. if (sortByDurationEnabled) {
  391. sortByDuration();
  392. }
  393.  
  394. }
  395.  
  396. // this one will only be run when the toggle is flipped because it consists of two other initializes that are already called upon mutation/page load
  397. function initializeSortWithinPlaylists() {
  398. initializeSortByTrophy();
  399. initializeSortByDuration();
  400. }
  401.  
  402. function initializeCursorHide() {
  403. var cursorHideStateEnabled = getToggleState('cursorHideState');
  404. if (cursorHideStateEnabled) {
  405. cursorHide(true);
  406. } else {
  407. cursorHide(false);
  408. }
  409. }
  410.  
  411. function hideVideos() {
  412. var hideWatchedEnabled = getToggleState('hideWatchedState');
  413. var hidePaidContentEnabled = getToggleState('hidePaidContentState');
  414.  
  415. // Use findVideoULs to get the <ul> elements
  416. const ulElements = findVideoULs(true);
  417.  
  418. // Get all <li> elements within the found <ul> elements
  419. const liElements = ulElements.flatMap(ul => Array.from(ul.querySelectorAll('li')));
  420.  
  421. const savedFilterWords = localStorage.getItem('savedFilterWords') || ''; // Handle null case
  422. const filterWords = savedFilterWords ? savedFilterWords.split(/,\s*|\s+/).map(word => word.trim().toLowerCase()) : [];
  423. const hidePaidContentState = getToggleState('hidePaidContentState');
  424. const hideWatchedState = getToggleState('hideWatchedState');
  425.  
  426. liElements.forEach(function(li) {
  427. const liTextContent = li.textContent.toLowerCase();
  428.  
  429. // Check if the current <li> has a watched indicator, excluding those with the 'hidden' class
  430. const watchedDiv = li.querySelector('.watchedVideoText, .watchedVideo');
  431. const isWatched = watchedDiv && !(watchedDiv.classList.contains('watchedVideoText') && watchedDiv.classList.contains('hidden'));
  432.  
  433. // Selectors for paid/premium/private content
  434. const priceSpan = li.querySelector('span.price');
  435. const premiumIcon = li.querySelector('.premiumicon'); // Corrected selector
  436. const aHrefJavaVoid = li.querySelector('a');
  437. const privateOverlay = li.querySelector('img.privateOverlay');
  438.  
  439. // Determine if the video should be hidden
  440. const shouldHideByFilterWords = filterWords.length > 0 && filterWords.some(word => word.length >= 3 && liTextContent.includes(word));
  441. const shouldHideWatchedVideos = hideWatchedState && isWatched; // Corrected condition to check isWatched
  442. const shouldHidePaidContent = hidePaidContentState && (priceSpan || premiumIcon || privateOverlay || (aHrefJavaVoid && aHrefJavaVoid.getAttribute('href') === 'javascript:void(0)'));
  443.  
  444. // Hide or show the <li> element based on conditions
  445. li.style.display = (shouldHideByFilterWords || shouldHideWatchedVideos || shouldHidePaidContent) ? 'none' : 'block';
  446. });
  447. }
  448.  
  449.  
  450.  
  451.  
  452. // Utility function to find all <ul> elements with class 'videos',
  453. // excluding those with specified IDs
  454. // used for sortByDuration and sortByTrophy
  455. function findVideoULs(sortWithinPlaylistsEnabled = true) {
  456. // Define IDs for <ul> elements that should be excluded when sorting within playlists is disabled
  457. const playlistIdsToExclude = ['videoPlaylist', 'videoPlaylistSection', 'playListSection'];
  458.  
  459. // Get all <ul> elements with class 'videos' or 'videoList'
  460. const allVideoULs = document.querySelectorAll('ul.videos, ul.videoList');
  461.  
  462. // Convert NodeList to an Array
  463. const videoULArray = Array.from(allVideoULs);
  464.  
  465. // Filter <ul> elements: Exclude playlists if sorting within playlists is disabled
  466. const filteredULs = videoULArray.filter(ul => {
  467. // Exclude IDs only when sortWithinPlaylistsEnabled is false
  468. const shouldExclude = !sortWithinPlaylistsEnabled && playlistIdsToExclude.includes(ul.id);
  469. // Include the <ul> if it should not be excluded
  470. return !shouldExclude;
  471. });
  472.  
  473. return filteredULs;
  474. }
  475.  
  476.  
  477. function sortByDuration() {
  478. function parseDuration(durationString) {
  479. const [minutes, seconds] = durationString.split(':').map(Number);
  480. return minutes * 60 + seconds;
  481. }
  482.  
  483. function sortLiElementsByDurationDesc(ulElement) {
  484. const liElements = Array.from(ulElement.querySelectorAll('li'))
  485. .filter(li => li.querySelector('.duration'));
  486.  
  487. if (liElements.length === 0) {
  488. console.warn(`No elements with duration found in the list container.`);
  489. return;
  490. }
  491.  
  492. liElements.sort((a, b) => {
  493. const durationA = parseDuration(a.querySelector('.duration').textContent);
  494. const durationB = parseDuration(b.querySelector('.duration').textContent);
  495. return durationB - durationA;
  496. });
  497.  
  498. liElements.forEach(li => {
  499. ulElement.appendChild(li);
  500. });
  501. }
  502.  
  503. // Use forced disable toggle or check the actual toggle state
  504. const sortWithinPlaylistsEnabled = getToggleState('sortWithinPlaylistsState');
  505. const ulsToSort = findVideoULs(sortWithinPlaylistsEnabled);
  506.  
  507. if (ulsToSort.length === 0) {
  508. console.warn('No <ul> elements with class "videos" found.');
  509. return;
  510. }
  511.  
  512. ulsToSort.forEach(ulElement => {
  513. sortLiElementsByDurationDesc(ulElement);
  514. });
  515. }
  516.  
  517. function sortByTrophy() {
  518. function sortLiElementsByTrophy(ulElement) {
  519. const liElements = Array.from(ulElement.querySelectorAll('li'));
  520. const freePremiumVideoItems = [];
  521. const otherItems = [];
  522.  
  523. liElements.forEach(li => {
  524. const childSpan = li.querySelector('span.award-icon');
  525. if (childSpan) {
  526. freePremiumVideoItems.push(li);
  527. } else {
  528. otherItems.push(li);
  529. }
  530. });
  531.  
  532. const reorderedLiElements = freePremiumVideoItems.concat(otherItems);
  533. reorderedLiElements.forEach(li => {
  534. ulElement.appendChild(li);
  535. });
  536. }
  537.  
  538. // Use forced disable toggle or check the actual toggle state
  539. const sortWithinPlaylistsEnabled = getToggleState('sortWithinPlaylistsState');
  540. const ulsToSort = findVideoULs(sortWithinPlaylistsEnabled);
  541.  
  542. if (ulsToSort.length === 0) {
  543. console.warn('No <ul> elements with class "videos" found.');
  544. return;
  545. }
  546.  
  547. ulsToSort.forEach(ulElement => {
  548. sortLiElementsByTrophy(ulElement);
  549. });
  550. }
  551.  
  552. function sortByDurationButton() {
  553. function parseDuration(durationString) {
  554. const [minutes, seconds] = durationString.split(':').map(Number);
  555. return minutes * 60 + seconds;
  556. }
  557.  
  558. function sortLiElementsByDurationDesc(ulElement) {
  559. const liElements = Array.from(ulElement.querySelectorAll('li'))
  560. .filter(li => li.querySelector('.duration'));
  561.  
  562. if (liElements.length === 0) {
  563. console.warn(`No elements with duration found in the list container.`);
  564. return;
  565. }
  566.  
  567. liElements.sort((a, b) => {
  568. const durationA = parseDuration(a.querySelector('.duration').textContent);
  569. const durationB = parseDuration(b.querySelector('.duration').textContent);
  570. return durationB - durationA;
  571. });
  572.  
  573. liElements.forEach(li => {
  574. ulElement.appendChild(li);
  575. });
  576. }
  577.  
  578. const ulsToSort = findVideoULs(true);
  579.  
  580. if (ulsToSort.length === 0) {
  581. console.warn('No <ul> elements with class "videos" found.');
  582. return;
  583. }
  584.  
  585. ulsToSort.forEach(ulElement => {
  586. sortLiElementsByDurationDesc(ulElement);
  587. });
  588. }
  589.  
  590. function sortByTrophyButton() {
  591. function sortLiElementsByTrophy(ulElement) {
  592. const liElements = Array.from(ulElement.querySelectorAll('li'));
  593. const freePremiumVideoItems = [];
  594. const otherItems = [];
  595.  
  596. liElements.forEach(li => {
  597. const childSpan = li.querySelector('span.award-icon');
  598. if (childSpan) {
  599. freePremiumVideoItems.push(li);
  600. } else {
  601. otherItems.push(li);
  602. }
  603. });
  604.  
  605. const reorderedLiElements = freePremiumVideoItems.concat(otherItems);
  606. reorderedLiElements.forEach(li => {
  607. ulElement.appendChild(li);
  608. });
  609. }
  610.  
  611. // Use forced disable toggle or check the actual toggle state
  612. const ulsToSort = findVideoULs(true);
  613.  
  614. if (ulsToSort.length === 0) {
  615. console.warn('No <ul> elements with class "videos" found.');
  616. return;
  617. }
  618.  
  619. ulsToSort.forEach(ulElement => {
  620. sortLiElementsByTrophy(ulElement);
  621. });
  622. }
  623.  
  624.  
  625.  
  626. // this simulates a click on the mute button in the videoplayer
  627. function clickMuteButton() {
  628. function simulateMouse(element, eventType) {
  629. const event = new Event(eventType, {
  630. view: window,
  631. bubbles: true,
  632. cancelable: true
  633. });
  634. element.dispatchEvent(event);
  635. }
  636. const muteDivs = document.querySelectorAll('div.mgp_volume > div[data-text="Mute"]');
  637.  
  638. if (muteDivs.length === 0) return;
  639.  
  640. muteDivs.forEach((div, index) => {
  641. simulateMouse(div, 'mouseover');
  642. simulateMouse(div, 'focus');
  643. simulateMouse(div, 'mousedown');
  644. simulateMouse(div, 'mouseup');
  645. simulateMouse(div, 'click');
  646.  
  647. if (div) {
  648. const event = new MouseEvent('mouseover', {
  649. view: window,
  650. bubbles: true
  651. });
  652. div.dispatchEvent(event);
  653. }
  654. });
  655.  
  656. log(`${muteDivs.length} video elements were muted.`);
  657. }
  658.  
  659. // Hide the load more buttons, all the items should already show
  660. function hideElements() {
  661. log('Hiding elements..');
  662.  
  663. // Hide the welcoming messages regarding different countries and the store
  664. if (document.getElementById('countryRedirectMessage')) {
  665. document.getElementById('countryRedirectMessage').style.display = 'none';
  666. }
  667.  
  668. if (document.getElementById('welcome')) {
  669. document.getElementById('welcome').style.display = 'none';
  670. }
  671.  
  672. // This will hide the ugly long empty blocks under videos while going through pages
  673. // Select all divs with the class 'pornInLangWrapper'
  674. const divs = document.querySelectorAll('div.pornInLangWrapper');
  675.  
  676. // Loop through each div and hide it
  677. divs.forEach(div => {
  678. div.style.display = 'none';
  679. });
  680.  
  681.  
  682. // remove load more buttons
  683. // under the video
  684. if (document.getElementById('loadMoreRelatedVideosCenter')) {
  685. document.getElementById('loadMoreRelatedVideosCenter').style.display = 'none';
  686. }
  687. // on the right side of the video
  688. const recommendedLoadMoreElements = document.querySelectorAll('[data-label="recommended_load_more"]');
  689. recommendedLoadMoreElements.forEach(element => {
  690. element.style.display = 'none';
  691. });
  692.  
  693. }
  694.  
  695. // Usage
  696. // cursorHide(true); // To enable hiding the cursor
  697. // cursorHide(false); // To disable hiding the cursor
  698. // this makes use of the class that is added to the videowrapper when a video is playing, it is removed when paused
  699. function cursorHide(enable) {
  700. // Check if the style element already exists
  701. let existingStyle = document.getElementById('cursor-hide-style');
  702.  
  703. if (enable) {
  704. if (!existingStyle) {
  705. log("cursorHide is enabled but the style doesn't exist yet, creating cursorHide style..");
  706. // Create and append the style element if it doesn't exist
  707. const style = document.createElement('style');
  708. style.id = 'cursor-hide-style';
  709. style.textContent = `
  710. /* Define the cursor hiding animation */
  711. @keyframes hideCursor {
  712. 0% {
  713. cursor: default;
  714. }
  715. 99% {
  716. cursor: default;
  717. }
  718. 100% {
  719. cursor: none;
  720. }
  721. }
  722.  
  723. /* Apply the animation on hover */
  724. .mgp_playingState {
  725. animation: none;
  726. }
  727.  
  728. .mgp_playingState:hover {
  729. animation: hideCursor 3s forwards;
  730. }
  731. `;
  732.  
  733. document.head.appendChild(style);
  734. log('cursorHide style added.');
  735. }
  736. } else {
  737. if (existingStyle) {
  738. log("cursorHide is disabled but the style still exists, removing cursorHide style..");
  739. // Remove the style element if it exists
  740. existingStyle.remove();
  741. log('cursorHide style removed.');
  742. }
  743. }
  744. }
  745.  
  746. // Change the language of the website to English if that is not the case
  747. // Since this script sometimes uses words to function this is still neccesary
  748. function redirectToEnglish() {
  749. var redirectToEnglishStateEnabled = getToggleState('redirectToEnglishState');
  750. if (redirectToEnglishStateEnabled) {
  751. const isNotEnglish = () => {
  752. const languageDropdownLI = document.querySelector('li.languageDropdown');
  753. return !languageDropdownLI || (languageDropdownLI.querySelector('span.networkTab') && languageDropdownLI.querySelector('span.networkTab').textContent.trim().toLowerCase() !== 'en');
  754. };
  755.  
  756. const findEnLink = () => {
  757. const enLanguageOptionLI = document.querySelector('li[data-lang="en"]');
  758. if (!enLanguageOptionLI) return null;
  759.  
  760. const enLanguageLink = enLanguageOptionLI.querySelector('a.networkTab');
  761. return !enLanguageLink ? console.error('Anchor element with class "networkTab" not found within the specified <li> to check the current language.') : enLanguageLink;
  762. };
  763.  
  764. function checkAndClick() {
  765. if (isNotEnglish()) {
  766. const enLink = findEnLink();
  767.  
  768. // If we've already tried clicking and it didn't work, stop trying
  769. if (!enLink) return log('No English link to click. Giving up.');
  770.  
  771. enLink.click();
  772. log('Clicked the link:', enLink);
  773. } else {
  774. log('Current language is already English. No action needed.');
  775. }
  776. }
  777.  
  778. setTimeout(checkAndClick, 1000); // Adjust delay if necessary
  779. }
  780. }
  781.  
  782.  
  783. // this adds a transparant red square over items if you press delete
  784. // to show you which you have deleted when editing a playlist
  785. function addRedOverlay(element) {
  786. var overlay = document.createElement('div');
  787. overlay.style.position = 'absolute';
  788. overlay.style.top = '0';
  789. overlay.style.left = '0';
  790. overlay.style.width = '100%';
  791. overlay.style.height = '100%';
  792. overlay.style.backgroundColor = 'red';
  793. overlay.style.opacity = '0.5';
  794. overlay.style.pointerEvents = 'none';
  795.  
  796. var parentLi = element.closest('li');
  797. if (parentLi) {
  798. parentLi.style.position = 'relative';
  799. parentLi.appendChild(overlay);
  800. }
  801. }
  802.  
  803. // when editing playlists, whenever the delete button on a video is clicked, this will create a transparent red square around the video
  804. document.addEventListener('click', function(event) {
  805. if (event.target && event.target.matches('button[onclick="deleteFromPlaylist(this);"]')) {
  806. addRedOverlay(event.target);
  807. log('Added red overlay to the playlist item');
  808. }
  809. });
  810.  
  811. // run updateToggleStates when changing to a tab (using visibilityState to see if it is active/visible) so the state will be synced:
  812. document.addEventListener('visibilitychange', function() {
  813. if (document.visibilityState === 'visible') {
  814. log('Tab is visible, updating toggle states..');
  815. // update toggle states
  816. updateToggleStates();
  817. // this should trigger a DOM change, so runOnMutation should run and things should update
  818.  
  819. // make sure the menu visibility matches the state
  820. syncSideMenu();
  821.  
  822. // change language to English
  823. redirectToEnglish();
  824.  
  825. // check if the cursor should be hidden
  826. initializeCursorHide();
  827. }
  828. });
  829.  
  830. // this will be run when the observer spots mutations
  831. function initializeEverything() {
  832. initializeSortByTrophy();
  833. initializeSortByDuration();
  834. initializeMuteVideos();
  835. hideVideos();
  836. }
  837.  
  838. // run this when the page is fully loaded
  839. window.onload = function() {
  840. // hide 'Load More' buttons and the like
  841. hideElements();
  842.  
  843. // create the menu
  844. createSideMenu();
  845.  
  846. // change lang to english
  847. redirectToEnglish();
  848.  
  849. // check if the cursor should be hidden
  850. initializeCursorHide();
  851.  
  852. // no need to initialize everything manually, that will trigger on page load (runOnMutation will take care of the rest)
  853. // but this makes the page ready to go as soon as it is done loading, so we will keep it for now
  854. initializeEverything();
  855.  
  856. // keep checking for mutations using an observer
  857. function runOnMutation() {
  858. clearTimeout(runOnMutation.timeout);
  859. runOnMutation.timeout = setTimeout(() => {
  860. initializeEverything();
  861. }, 500);
  862. }
  863.  
  864. const observer = new MutationObserver(function(mutations) {
  865. runOnMutation();
  866. });
  867.  
  868. observer.observe(document.body, {
  869. childList: true,
  870. subtree: true
  871. });
  872.  
  873. };
  874.  
  875. })();