PornHub Plus

A kinder PornHub. Because you're worth it.

  1. // ==UserScript==
  2. // @author LD
  3. // @version 1.0
  4. // @name PornHub Plus
  5. // @description A kinder PornHub. Because you're worth it.
  6. // @namespace LD
  7. // @date 2018-08-08
  8. // @include *pornhub.com/*
  9. // @run-at document-start
  10. // @grant none
  11. // @license Public Domain
  12. // @icon http://www.techthisoutnews.com/wp-content/uploads/2018/03/porn.jpg
  13. // @grant GM_addStyle
  14. // ==/UserScript==
  15.  
  16. (() => {
  17. const OPTIONS = {
  18. openWithoutPlaylist: true,
  19. showOnlyVerified: JSON.parse(localStorage.getItem('plus_showOnlyVerified')) || false,
  20. redirectToVideos: JSON.parse(localStorage.getItem('plus_redirectToVideos')) || false,
  21. autoresizePlayer: JSON.parse(localStorage.getItem('plus_autoresizePlayer')) || false,
  22. durationFilter: JSON.parse(localStorage.getItem('plus_durationFilter')) || { max: 0, min: 0 }
  23. }
  24. /* Styles - Shared between all "Plus" userscripts */
  25. const sharedStyles = `
  26. /* Our own elements */
  27.  
  28. .plus-buttons {
  29. background: rgba(27, 27, 27, 0.9);
  30. box-shadow: 0px 0px 12px rgba(20, 111, 223, 0.9);
  31. font-size: 12px;
  32. position: fixed;
  33. bottom: 10px;
  34. padding: 10px 22px 8px 24px;
  35. right: 0;
  36. z-index: 100;
  37. transition: all 0.3s ease;
  38.  
  39. /* Negative margin-right calculated later based on width of buttons */
  40. }
  41.  
  42. .plus-buttons:hover {
  43. box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.3);
  44. }
  45.  
  46. .plus-buttons .plus-button {
  47. margin: 10px 0;
  48. padding: 6px 15px;
  49. border-radius: 4px;
  50. font-weight: 700;
  51. display: block;
  52. position: relative;
  53. text-align: center;
  54. vertical-align: top;
  55. cursor: pointer;
  56. border: none;
  57. text-decoration: none;
  58. }
  59.  
  60. .plus-buttons a.plus-button {
  61. background: rgb(221, 221, 221);
  62. color: rgb(51, 51, 51);
  63. }
  64.  
  65. .plus-buttons a.plus-button:hover {
  66. background: rgb(187, 187, 187);
  67. color: rgb(51, 51, 51);
  68. }
  69.  
  70. .plus-buttons a.plus-button.plus-button-isOn {
  71. background: rgb(20, 111, 223);
  72. color: rgb(255, 255, 255);
  73. }
  74.  
  75. .plus-buttons a.plus-button.plus-button-isOn:hover {
  76. background: rgb(0, 91, 203);
  77. color: rgb(255, 255, 255);
  78. }
  79.  
  80. .plus-hidden {
  81. display: none !important;
  82. }
  83. `;
  84. /* Styles - Color theme */
  85. const themeStyles = `
  86. .plus-buttons {
  87. box-shadow: 0px 0px 12px rgba(255, 153, 0, 0.85);
  88. }
  89.  
  90. .plus-buttons:hover {
  91. box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.3);
  92. }
  93.  
  94. .plus-buttons a.plus-button {
  95. background: rgb(47, 47, 47);
  96. color: rgb(172, 172, 172);
  97. }
  98.  
  99. .plus-buttons a.plus-button:hover {
  100. background: rgb(79, 79, 79);
  101. color: rgb(204, 204, 204);
  102. }
  103.  
  104. .plus-buttons a.plus-button.plus-button-isOn {
  105. background: rgb(255, 153, 0);
  106. color: rgb(0, 0, 0);
  107. }
  108.  
  109. .plus-buttons a.plus-button.plus-button-isOn:hover {
  110. background: rgb(255, 153, 0);
  111. color: rgb(255, 255, 255);
  112. }
  113. `;
  114. /* Styles - General site-specific */
  115. const generalStyles = `
  116. /* Hide elements */
  117.  
  118. .abovePlayer,
  119. .streamatesModelsContainer,
  120. #headerUpgradePremiumBtn,
  121. #headerUploadBtn,
  122. #PornhubNetworkBar,
  123. #js-abContainterMain,
  124. #hd-rightColVideoPage > :not(#relatedVideosVPage) {
  125. display: none !important;
  126. }
  127.  
  128. /* Show all playlists without scrolling in "add to" */
  129.  
  130. .slimScrollDiv {
  131. height: auto !important;
  132. }
  133.  
  134. #scrollbar_watch {
  135. max-height: unset !important;
  136. }
  137.  
  138. /* Hide premium video from related videos sidebar */
  139.  
  140. #relateRecommendedItems li:nth-of-type(5) {
  141. display: none !important;
  142. }
  143.  
  144. /* Prevent animating player size change on each page load */
  145.  
  146. #main-container .video-wrapper #player.wide {
  147. transition: none !important;
  148. }
  149. `;
  150. /*
  151. * Run after page has loaded
  152. */
  153. window.addEventListener('DOMContentLoaded', () => {
  154. const player = document.querySelector('#player');
  155. const video = document.querySelector('video');
  156. // const largePlayerButton = document.querySelector('.mhp1138_cinema');
  157. /*
  158. * Use wide player by default
  159. */
  160. if (player && OPTIONS.autoresizePlayer) {
  161. player.classList.remove('original');
  162. player.classList.add('wide');
  163. document.querySelector('#hd-rightColVideoPage').classList.add('wide');
  164. setTimeout(() => {
  165. document.querySelector('.mhp1138_cinema').classList.add('mhp1138_active');
  166. }, 2000);
  167. // if (video.readyState >= 2) {
  168. // // Video cached and ready
  169. // largePlayerButton.dispatchEvent(new MouseEvent('mouseup'));
  170. // } else {
  171. // // Wait for video to be ready
  172. // video.addEventListener('canplay', function onCanPlay() {
  173. // /* Click large player button */
  174. // largePlayerButton.dispatchEvent(new MouseEvent('mouseup'));
  175. //
  176. // /* Only run once */
  177. // video.removeEventListener('canplay', onCanPlay, false);
  178. // });
  179. // }
  180. }
  181. /*
  182. * Clicking a video on a playlist page opens it without the playlist at the top
  183. *
  184. * If this option is disabled, add it as a link after the video title.
  185. */
  186.  
  187. if (OPTIONS.openWithoutPlaylist) {
  188. const playlistLinks = document.querySelectorAll('#playlistWrapper #videoPlaylist li a');
  189. for (link of playlistLinks) link.href = link.href.replace('pkey', 'nopkey');
  190. } else {
  191. const links = document.querySelectorAll('#playlistWrapper #videoPlaylist li .title a');
  192. for (link of links) {
  193. let newLink = document.createElement('a');
  194. newLink.href = link.href.replace('pkey', 'nopkey');
  195. newLink.appendChild(document.createTextNode('[↗]'));
  196. link.parentNode.appendChild(newLink);
  197. }
  198. }
  199. /*
  200. * Allow scrolling the page when mouse hovers playlists in "add to"
  201. */
  202. /* Clone playlist scroll container to remove listeners that preventDefault() */
  203. var scrollContainer = document.getElementById('scrollbar_watch');
  204. if (scrollContainer) {
  205. var newScrollContainer = scrollContainer.cloneNode(true);
  206. scrollContainer.parentNode.replaceChild(newScrollContainer, scrollContainer);
  207. }
  208. /*
  209. * Automatically "load more" to show all videos on user video pages
  210. var autoloadTimer = null;
  211. var loadMoreButton = document.getElementById('moreDataBtn');
  212. function autoloadMore() {
  213. if (loadMoreButton.style.display === 'none') {
  214. clearInterval(autoloadTimer);
  215. } else {
  216. loadMoreButton.onclick();
  217. }
  218. }
  219. if (loadMoreButton) {
  220. autoloadTimer = setInterval(autoloadMore, 5000);
  221. }*/
  222. /*
  223. * Add buttons for certain options
  224. */
  225. /* Buttons container */
  226. let buttons = document.createElement('div');
  227. let scrollButton = document.createElement('a');
  228. let scrollButtonText = document.createElement('span');
  229. let verifiedButton = document.createElement('a');
  230. let verifiedButtonText = document.createElement('span');
  231. let verifiedButtonState = OPTIONS.showOnlyVerified ? 'plus-button-isOn' : 'plus-button-isOff';
  232. let redirectToVideosButton = document.createElement('a');
  233. let redirectToVideosButtonText = document.createElement('span');
  234. let redirectToVideosButtonState = OPTIONS.redirectToVideos ? 'plus-button-isOn' : 'plus-button-isOff';
  235. let autoresizeButton = document.createElement('a');
  236. let autoresizeButtonText = document.createElement('span');
  237. let autoresizeButtonState = OPTIONS.autoresizePlayer ? 'plus-button-isOn' : 'plus-button-isOff';
  238. let durationShortButton = document.createElement('a');
  239. let durationShortButtonText = document.createElement('span');
  240. let durationShortButtonState = !OPTIONS.durationFilter.min ? 'plus-button-isOn' : 'plus-button-isOff';
  241. buttons.classList.add('plus-buttons');
  242. scrollButtonText.textContent = "Scroll to top";
  243. scrollButtonText.classList.add('text');
  244. scrollButton.appendChild(scrollButtonText);
  245. scrollButton.classList.add('plus-button');
  246. scrollButton.addEventListener('click', () => {
  247. window.scrollTo({ top: 0 });
  248. });
  249. buttons.appendChild(scrollButton);
  250. verifiedButtonText.textContent = 'Show only verified';
  251. verifiedButtonText.classList.add('text');
  252. verifiedButton.appendChild(verifiedButtonText);
  253. verifiedButton.classList.add(verifiedButtonState, 'plus-button');
  254. verifiedButton.addEventListener('click', () => {
  255. setShowOnlyVerified(!OPTIONS.showOnlyVerified);
  256. });
  257. buttons.appendChild(redirectToVideosButton);
  258. redirectToVideosButtonText.textContent = 'Redirect profiles to uploads';
  259. redirectToVideosButtonText.classList.add('text');
  260. redirectToVideosButton.appendChild(redirectToVideosButtonText);
  261. redirectToVideosButton.classList.add(redirectToVideosButtonState, 'plus-button');
  262. redirectToVideosButton.addEventListener('click', () => {
  263. OPTIONS.redirectToVideos = !OPTIONS.redirectToVideos;
  264. localStorage.setItem('plus_redirectToVideos', OPTIONS.redirectToVideos);
  265. if (OPTIONS.redirectToVideos) {
  266. redirectToVideosButton.classList.replace('plus-button-isOff', 'plus-button-isOn');
  267. } else {
  268. redirectToVideosButton.classList.replace('plus-button-isOn', 'plus-button-isOff');
  269. }
  270. });
  271. buttons.appendChild(autoresizeButton);
  272. durationShortButtonText.textContent = 'Show short videos (< 8 min)';
  273. durationShortButtonText.classList.add('text');
  274. durationShortButton.appendChild(durationShortButtonText);
  275. durationShortButton.classList.add(durationShortButtonState, 'plus-button');
  276. durationShortButton.addEventListener('click', () => {
  277. OPTIONS.durationFilter.min = OPTIONS.durationFilter.min ? 0 : 8;
  278. localStorage.setItem('plus_durationFilter', JSON.stringify(OPTIONS.durationFilter));
  279. if (!OPTIONS.durationFilter.min) {
  280. durationShortButton.classList.replace('plus-button-isOff', 'plus-button-isOn');
  281. updateDurationFilter();
  282. } else {
  283. durationShortButton.classList.replace('plus-button-isOn', 'plus-button-isOff');
  284. updateDurationFilter();
  285. }
  286. });
  287. buttons.appendChild(durationShortButton);
  288. autoresizeButtonText.textContent = 'Auto-resize player';
  289. autoresizeButtonText.classList.add('text');
  290. autoresizeButton.appendChild(autoresizeButtonText);
  291. autoresizeButton.classList.add(autoresizeButtonState, 'plus-button');
  292. autoresizeButton.addEventListener('click', () => {
  293. OPTIONS.autoresizePlayer = !OPTIONS.autoresizePlayer;
  294. localStorage.setItem('plus_autoresizePlayer', OPTIONS.autoresizePlayer);
  295. if (OPTIONS.autoresizePlayer) {
  296. autoresizeButton.classList.replace('plus-button-isOff', 'plus-button-isOn');
  297. } else {
  298. autoresizeButton.classList.replace('plus-button-isOn', 'plus-button-isOff');
  299. }
  300. });
  301. document.body.appendChild(buttons);
  302. if (window.location.href.includes('/playlist/')) {
  303. buttons.appendChild(verifiedButton);
  304.  
  305. setTimeout(() => {
  306. setShowOnlyVerified(OPTIONS.showOnlyVerified);
  307. }, 1000);
  308. }
  309. /* Redirect profile page to all uploads, except if we just came from there */
  310. if (
  311. /^https:\/\/www\.pornhub\.com\/pornstar\/([^\/]+)$/.test(window.location.href) ||
  312. /^https:\/\/www\.pornhub\.com\/model\/([^\/]+)$/.test(window.location.href) ||
  313. /^https:\/\/www\.pornhub\.com\/users\/([^\/]+)$/.test(window.location.href) ||
  314. /^https:\/\/www\.pornhub\.com\/channels\/([^\/]+)$/.test(window.location.href)) {
  315. if (OPTIONS.redirectToVideos && !/.+\/videos\/upload.*/.test(document.referrer)) {
  316. window.location.href = window.location.href + '/videos/upload';
  317. }
  318. }
  319. function setShowOnlyVerified(state) {
  320. OPTIONS.showOnlyVerified = state;
  321. localStorage.setItem('plus_showOnlyVerified', state);
  322. if (state) {
  323. verifiedButton.classList.replace('plus-button-isOff', 'plus-button-isOn');
  324. } else {
  325. verifiedButton.classList.replace('plus-button-isOn', 'plus-button-isOff');
  326. }
  327. document.querySelectorAll('.videoBox').forEach((box) => {
  328. if (!box.innerHTML.includes('Video of verified member')) {
  329. if (state) {
  330. box.classList.add('plus-hidden');
  331. } else {
  332. box.classList.remove('plus-hidden');
  333. }
  334. }
  335. });
  336. }
  337. function updateDurationFilter() {
  338. document.querySelectorAll('.videoBox').forEach((box) => {
  339. let durationParts = box.querySelector('.duration').textContent.split(":");
  340. let duration = { minutes: parseInt(durationParts[0]), seconds: parseInt(durationParts[1]) }
  341. if (duration.minutes >= OPTIONS.durationFilter.min &&
  342. (!OPTIONS.durationFilter.max || duration.minutes <= OPTIONS.durationFilter.max)) {
  343. box.classList.remove('plus-hidden');
  344. } else {
  345. box.classList.add('plus-hidden');
  346. }
  347. });
  348. }
  349. let loadMoreButton = document.querySelector('.more_related_btn');
  350. if (loadMoreButton)
  351. loadMoreButton.addEventListener('click', updateDurationFilter);
  352. updateDurationFilter();
  353. /*
  354. * Add styles
  355. */
  356. GM_addStyle(sharedStyles);
  357. GM_addStyle(themeStyles);
  358. GM_addStyle(generalStyles);
  359. /*
  360. * Add dynamic styles
  361. */
  362. const dynamicStyles = `
  363. .plus-buttons {
  364. margin-right: -${buttons.getBoundingClientRect().width - 23}px;
  365. }
  366.  
  367. .plus-buttons:hover {
  368. margin-right: 0;
  369. }
  370. `;
  371. GM_addStyle(dynamicStyles);
  372. });
  373. })();