PH toolbox

PornHub toolbox (https://codeberg.org/aolko/userscripts)

  1. // ==UserScript==
  2. // @name PH toolbox
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6.5
  5. // @description PornHub toolbox (https://codeberg.org/aolko/userscripts)
  6. // @author aolko
  7. // @license GPL-3.0-or-later
  8. // @match *://*.pornhub.com/*
  9. // @match *pornhub.com*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=pornhub.com
  11. // @grant GM_getResourceText
  12. // @grant GM_addStyle
  13. // @grant GM_openInTab
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_listValues
  17. // @grant GM_registerMenuCommand
  18. // @resource PH_CSS https://codeberg.org/aolko/userscripts/raw/branch/master/ph_toolbox/ph.style.css?v1
  19. // @require https://code.jquery.com/jquery-3.6.1.min.js
  20. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  21. // ==/UserScript==
  22.  
  23. /*
  24. Check the official repo: https://codeberg.org/aolko/userscripts
  25. */
  26.  
  27. /*! instant.page v5.1.1 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
  28. let t,e;const n=new Set,o=document.createElement("link"),i=o.relList&&o.relList.supports&&o.relList.supports("prefetch")&&window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype,s="instantAllowQueryString"in document.body.dataset,a="instantAllowExternalLinks"in document.body.dataset,r="instantWhitelist"in document.body.dataset,c="instantMousedownShortcut"in document.body.dataset,d=1111;let l=65,u=!1,f=!1,m=!1;if("instantIntensity"in document.body.dataset){const t=document.body.dataset.instantIntensity;if("mousedown"==t.substr(0,"mousedown".length))u=!0,"mousedown-only"==t&&(f=!0);else if("viewport"==t.substr(0,"viewport".length))navigator.connection&&(navigator.connection.saveData||navigator.connection.effectiveType&&navigator.connection.effectiveType.includes("2g"))||("viewport"==t?document.documentElement.clientWidth*document.documentElement.clientHeight<45e4&&(m=!0):"viewport-all"==t&&(m=!0));else{const e=parseInt(t);isNaN(e)||(l=e)}}if(i){const n={capture:!0,passive:!0};if(f||document.addEventListener("touchstart",function(t){e=performance.now();const n=t.target.closest("a");if(!h(n))return;v(n.href)},n),u?c||document.addEventListener("mousedown",function(t){const e=t.target.closest("a");if(!h(e))return;v(e.href)},n):document.addEventListener("mouseover",function(n){if(performance.now()-e<d)return;if(!("closest"in n.target))return;const o=n.target.closest("a");if(!h(o))return;o.addEventListener("mouseout",p,{passive:!0}),t=setTimeout(()=>{v(o.href),t=void 0},l)},n),c&&document.addEventListener("mousedown",function(t){if(performance.now()-e<d)return;const n=t.target.closest("a");if(t.which>1||t.metaKey||t.ctrlKey)return;if(!n)return;n.addEventListener("click",function(t){1337!=t.detail&&t.preventDefault()},{capture:!0,passive:!1,once:!0});const o=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1,detail:1337});n.dispatchEvent(o)},n),m){let t;(t=window.requestIdleCallback?t=>{requestIdleCallback(t,{timeout:1500})}:t=>{t()})(()=>{const t=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){const n=e.target;t.unobserve(n),v(n.href)}})});document.querySelectorAll("a").forEach(e=>{h(e)&&t.observe(e)})})}}function p(e){e.relatedTarget&&e.target.closest("a")==e.relatedTarget.closest("a")||t&&(clearTimeout(t),t=void 0)}function h(t){if(t&&t.href&&(!r||"instant"in t.dataset)&&(a||t.origin==location.origin||"instant"in t.dataset)&&["http:","https:"].includes(t.protocol)&&("http:"!=t.protocol||"https:"!=location.protocol)&&(s||!t.search||"instant"in t.dataset)&&!(t.hash&&t.pathname+t.search==location.pathname+location.search||"noInstant"in t.dataset))return!0}function v(t){if(n.has(t))return;const e=document.createElement("link");e.rel="prefetch",e.href=t,document.head.appendChild(e),n.add(t)}
  29.  
  30. /* PageAutomator */
  31. function PageAutomator(){this.hover=function(t){return document.querySelector(t).dispatchEvent(new MouseEvent("mouseover")),this},this.click=function(t,e="left"){var n=document.querySelector(t);return n?("left"===e?n.dispatchEvent(new MouseEvent("click")):"right"===e&&n.dispatchEvent(new MouseEvent("contextmenu")),this):(console.log("Error: element not found"),this)},this.scroll=function(t){return window.scrollBy(0,t),this},this.scrollTo=function(t){return t.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"}),this},this.hold=function(t,e){var n=document.querySelector(t);return"left"===e?n.dispatchEvent(new MouseEvent("mousedown")):"right"===e&&n.dispatchEvent(new MouseEvent("mousedown",{button:2})),this},this.moveToPosition=function(t,e){return window.dispatchEvent(new MouseEvent("mousemove",{clientX:t,clientY:e})),this},this.moveToElement=function(t){var e=document.querySelector(t);if(!e)return console.log("Error: element not found"),this;var n=e.getBoundingClientRect(),o=n.left+window.pageXOffset+n.width/2,i=n.top+window.pageYOffset+n.height/2;return window.dispatchEvent(new MouseEvent("mousemove",{clientX:o,clientY:i})),this},this.getPosition=function(){var t={x:0,y:0};return document.addEventListener("mousemove",(function(e){t.x=e.clientX,t.y=e.clientY})),t},this.keyPress=function(t){var e=new KeyboardEvent("keypress",{key:t});return document.dispatchEvent(e),this},this.keyUp=function(t){var e=new KeyboardEvent("keyup",{key:t});return document.dispatchEvent(e),this},this.keyDown=function(t){var e=new KeyboardEvent("keydown",{key:t});return document.dispatchEvent(e),this},this.holdKey=function(t,e){var n={ctrl:17,shift:16,alt:18,win:91},o=new KeyboardEvent("keydown",{keyCode:n[t],which:n[t]});document.dispatchEvent(o),e();o=new KeyboardEvent("keyup",{keyCode:n[t],which:n[t]});return document.dispatchEvent(o),this},this.holdKeySequence=function(t,e){return Mousetrap.bind(t,(function(){e(),Mousetrap.unbind(t)}),"keydown"),this},this.setKeyState=function(t,e){if("numlock"===t){var n=new KeyboardEvent("keydown",{key:"NumLock",code:"NumLock"});document.dispatchEvent(n)}else if("scrolllock"===t){n=new KeyboardEvent("keydown",{key:"ScrollLock",code:"ScrollLock"});document.dispatchEvent(n)}else if("capslock"===t){n=new KeyboardEvent("keydown",{key:"CapsLock",code:"CapsLock"});document.dispatchEvent(n)}return this},this.blockInput=function(){return document.addEventListener("keydown",(function(t){t.preventDefault()})),document.addEventListener("mousedown",(function(t){t.preventDefault()})),this},this.wait=function(t){for(var e=(new Date).getTime(),n=e;n<e+t;)n=(new Date).getTime();return this},this.waitForElement=function(t){for(var e=document.querySelector(t);!e;)e=document.querySelector(t);return this},this.waitForMouse=function(t){for(var e=document.body.style.cursor;e!==t;)e=document.body.style.cursor;return this},this.ifElement=function(t,e,n){var o=document.querySelector(t);return"contains"===e?!!o.innerHTML.includes(n):"does not contain"===e?!o.innerHTML.includes(n):"is"===e?o.innerHTML===n:"is not"===e?o.innerHTML!==n:this},this.onElement=function(t,e){var n=document.querySelector(t);return new MutationObserver((function(t){return e.call(n),this})).observe(n,{attributes:!0,childList:!0,characterData:!0}),this},this.hasText=function(t,e){return t.textContent.trim().includes(e)},this.hasElement=function(t,e){return null!==t.querySelector(e)},this.showNotification=function(t,e){new Notification(t,{body:e});return this},this.showDialog=function(t,e){var n=document.createElement("dialog"),o=document.createElement("strong");o.innerHTML=t;var i=document.createElement("p");return i.innerHTML=e,n.appendChild(o),n.appendChild(i),document.body.appendChild(n),n.show(),this},this.showCustomDialog=function(t){var e=document.createElement("dialog");return e.innerHTML=t,document.body.appendChild(e),e.show(),this},this.getClipboardText=function(){return navigator.clipboard.readText().then((t=>t))},this.setClipboardText=function(t){return navigator.clipboard.writeText(t),this},this.clearClipboard=function(){return navigator.clipboard.writeText(""),this},this.ifUrl=function(t,e){(t.startsWith("/")&&window.location.pathname===t||"/"===t&&"/"===window.location.pathname||window.location.href===t)&&e()},this.navigate=function(t){window.location.href=t},this.get_domain=function(){return window.location.hostname},this.get_protocol=function(){return window.location.protocol},this.get_page=function(){return window.location.pathname},this.get_query=function(){return window.location.search}}
  32.  
  33. /* globals $ */
  34. var $ = window.jQuery;
  35.  
  36. var frame = document.createElement('div');
  37. document.body.appendChild(frame);
  38.  
  39. GM_config.init(
  40. {
  41. 'id': 'ph__config', // The id used for this instance of GM_config
  42. 'title': "⚙ PH toolbox settings",
  43. 'fields': // Fields object
  44. {
  45. 'labsFrontend': {
  46. 'label': 'Experimental frontend',
  47. 'section': 'Experimental',
  48. 'type': 'checkbox',
  49. 'default': false
  50. },
  51. 'Layout': {
  52. 'label': 'Layout',
  53. 'section': 'General',
  54. 'type': 'radio',
  55. 'options': ['Default','Basic'],
  56. 'default': 'Default'
  57. },
  58. 'HideShortVids': {
  59. 'label': 'Hide short videos',
  60. 'type': 'checkbox',
  61. 'default': false
  62. },
  63. 'ShortVidMin': {
  64. 'label': 'Minimum video length (in seconds)',
  65. 'type': 'int',
  66. 'min': 0,
  67. 'max': 600,
  68. 'default': 60
  69. }
  70. },
  71. 'events':
  72. {
  73. 'open': function(){
  74. GM_config.frame.setAttribute('style', `
  75. position: fixed; /* Stay in place */
  76. display: flex !important;
  77. flex-direction: column;
  78. align-items: center;
  79. z-index: 9999; /* Sit on top */
  80. left: 0;
  81. top: 0;
  82. width: 100%; /* Full width */
  83. height: 100%; /* Full height */
  84. overflow: auto; /* Enable scroll if needed */
  85. `);
  86. }
  87. },
  88. 'frame': frame,
  89. 'css': `
  90. #ph__config{background: hsl(0 0% 0% / .8);}
  91. #ph__config_wrapper{
  92. position: relative;
  93. top: 20%;
  94. margin:auto !important;
  95. background: #000;
  96. border-radius: 8px;
  97. max-width: 500px;
  98. border: 2px solid #ff9000;
  99. transition: all .5s ease-in-out;
  100. }
  101. #ph__config_header,.config_var{padding: 10px;}
  102. `
  103. });
  104.  
  105. if (typeof GM_registerMenuCommand === 'function') {
  106. GM_registerMenuCommand('Settings', function() {
  107. GM_config.open();
  108. });
  109. }
  110.  
  111. function getElementByXpath(path) {
  112. return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  113. }
  114.  
  115. function match(string, cases) {
  116. var matched = false;
  117. for (var key in cases) {
  118. if (key.includes("||")) {
  119. var orKeys = key.split("||");
  120. orKeys.forEach(function(orKey) {
  121. if (orKey === string) {
  122. cases[key]();
  123. matched = true;
  124. }
  125. });
  126. } else if (key === string) {
  127. cases[key]();
  128. matched = true;
  129. }
  130. }
  131. if (!matched && cases.hasOwnProperty("default")) {
  132. cases["default"]();
  133. } else if (!matched) {
  134. throw new Error(`No case found for ${string}`);
  135. }
  136. }
  137.  
  138.  
  139. function showVids(url){
  140. var full_url = ph__domain + url+"/videos";
  141. console.log(full_url);
  142. $(`body`).append(`
  143. <dialog class="ph_dialog ph_modelVideos__window">
  144. <div class="heading">
  145. <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Model's videos</h2></div>
  146. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  147. </div>
  148. <div class="ph_modelVideos__container"></div>
  149. </dialog>
  150. `)
  151. //$( "dialog.phmodelVideos__window" ).load( "${full_url}/videos #videosTab > div > div > div:nth-child(2)" );
  152. $.ajax({
  153. url:full_url,
  154. type: 'GET',
  155. cache: false,
  156. success: function(data){
  157. $('dialog.ph_modelVideos__window > .ph_modelVideos__container').html($(data).find('#videosTab > .sectionWrapper > .profileVids').html());
  158. }
  159. });
  160. };
  161.  
  162. function parseURL(url){
  163. var getQueryParams = function (query) {
  164. var params = {};
  165. new URLSearchParams(query).forEach(function (value, key) {
  166. var decodedKey = decodeURIComponent(key);
  167. var decodedValue = decodeURIComponent(value);
  168. // This key is part of an array
  169. if (decodedKey.endsWith("[]")) {
  170. decodedKey = decodedKey.replace("[]", "");
  171. params[decodedKey] || (params[decodedKey] = []);
  172. params[decodedKey].push(decodedValue);
  173. // Just a regular parameter
  174. } else {
  175. params[decodedKey] = decodedValue;
  176. }
  177. });
  178.  
  179. return params;
  180. };
  181.  
  182. var _url = new URL(url);
  183. _url._path = _url.pathname.split( '/' ).join(`#/#`).split(`#`);
  184. _url._path[0] = _url.origin;
  185. _url._params = getQueryParams(_url.searchParams);
  186. return _url;
  187. }
  188.  
  189. function secondsToTime(e) {
  190. var h = Math.floor(e / 3600).toString().padStart(2, '0'),
  191. m = Math.floor(e % 3600 / 60).toString().padStart(2, '0'),
  192. s = Math.floor(e % 60).toString().padStart(2, '0');
  193. if (h > 0) {
  194. // Include the hours value in the output string if it is greater than 0
  195. return h + ':' + m + ':' + s;
  196. } else {
  197. // Omit the hours value if it is 0 or less
  198. return m + ':' + s;
  199. }
  200. }
  201.  
  202.  
  203. function timeToSeconds(e) {
  204. // Split the input time string into an array of hours, minutes, and seconds
  205. var [m, s] = e.split(":");
  206. // Convert the hours, minutes, and seconds to integers and return the total number of seconds
  207. return parseInt(m, 10) * 60 + parseInt(s, 10);
  208. }
  209.  
  210. function shuffleMessages(messages) {
  211. for (let i = messages.length - 1; i > 0; i--) {
  212. let j = Math.floor(Math.random() * (i + 1));
  213. [messages[i], messages[j]] = [messages[j], messages[i]];
  214. }
  215. return messages;
  216. }
  217.  
  218. function getRandomMessage(dict) {
  219. let messages = shuffleMessages(dict);
  220. return messages[0];
  221. }
  222.  
  223. const dict__messages = {
  224. video: {
  225. malicious: [
  226. "This video is a scam and contains malicious content",
  227. "This video contains ads and is trying to sell you something",
  228. "This video is promoting premium accounts or external links",
  229. "This video contains external links that could be harmful"
  230. ],
  231. tease: [
  232. "This video might contain no climax or might contain external links",
  233. "This video might be edited to cut out content and might contain external links",
  234. "This video might contain purposely cut content and might contain external links"
  235. ],
  236. wrong_classification: [
  237. "This video is not in the correct category",
  238. "This video is not using the correct tags"
  239. ]
  240. },
  241. profile: {
  242. malicious: [
  243. "This page is a scam and contains malicious content",
  244. "This page contains ads and is trying to sell you something",
  245. "This page is promoting premium accounts or external links",
  246. "This page contains external links that could be harmful"
  247. ],
  248. tease: [
  249. "This page might contain no climax or might contain external links",
  250. "This page might be edited to cut out content and might contain external links",
  251. "This page might contain purposely cut content and might contain external links"
  252. ],
  253. wrong_classification: [
  254. "This page is not in the correct category",
  255. "This page is not using the correct tags"
  256. ],
  257. inactive_profile: [
  258. "This profile is fake or no longer active.",
  259. "This user has not been active on the site in some time or this page is fake.",
  260. "We're sorry, but this profile is no longer available or this page is fake.",
  261. "Unfortunately, the user associated with this profile is no longer active on the platform.",
  262. ]
  263. },
  264. something_went_wrong: [
  265. "Whoops, something broke here, try to reload the page",
  266. "There was an error with loading the data, please try again later",
  267. "There was a script error, please try again later"
  268. ]
  269. }
  270.  
  271.  
  272.  
  273. const ph__domain = window.location.origin;
  274.  
  275. // global namespace for placing cleanup functions in
  276. var cleanup = cleanup || {};
  277.  
  278. const ph__url = parseURL(window.location.href);
  279.  
  280. window.addEventListener("load", function(){
  281. 'use strict';
  282.  
  283. console.log("[PageAutomator] Automator started");
  284.  
  285. var automator = new PageAutomator();
  286.  
  287. console.log(`[PH toolbox] Started`);
  288.  
  289. // TODO: Update elements for theme refresh in PH
  290.  
  291. cleanup.page = function(){
  292. $(`#age-verification-container,#age-verification-wrapper,.abAlertShown,#js-abContainterMain`).remove();
  293. }
  294. cleanup.header = function(){
  295. var elements = [
  296. ".networkBarWrapper",
  297. "#countryRedirectMessage",
  298. "#welcome",
  299. "#leftMenu a.menuLink.customUpgradeBtn.removeAdLink",
  300. "#leftMenu a.menuLink.halfWidth.js-menuAnalytics[href*='https://www.modelhub.com/']",
  301. "#leftMenu a.menuLink.halfWidth.js-menuAnalytics[href*='https://www.pornhubapparel.com/']",
  302. "#leftMenu a.menuLink.halfWidth.js-menuAnalytics[href*='https://www.uviu.com/']",
  303. "#leftMenu a.menuLink.halfWidth.leftItem.js-menuAnalytics[href*='https://www.pornhub.com/sex/']",
  304. "#leftMenu button.menuLink.removeAdLink",
  305. //"#profileMenuDropdownScroll > div.menuSection > .menuLink:has(a[href*='/user/orders'])",
  306. "#profileMenuDropdownScroll > div.menuSection > .menuLink:has(a[href*='/user/verification'])",
  307. "#profileMenuDropdownScroll > div.menuSection > .menuLink > a[href*='/user/orders']",
  308. "header > #headerMenuContainer",
  309. "body > div.wrapper > div.container > div.frontListingWrapper > div:nth-child(1) > div",
  310. ];
  311. elements.forEach(function(el) {
  312. $(el).remove();
  313. });
  314. $(`#coummunityMenuItems > li:nth-child(3)`).remove();
  315. $(getElementByXpath(`/html/body/div[4]/div[3]/div[5]/div[2]/comment()[3]`)).next().remove();
  316. $(`#menuItem2 > div > div > div.leftPanel.videos > ul > li:nth-child(10)`).remove();
  317. $(`#dropdownHeaderSubMenu > div.innerHeaderSubMenu.trendingWrapper`).remove();
  318. $(`#menuItem3 > div > div > div.rightPanel`).remove();
  319. $(`#menuItem3 > div > div > div.leftPanel > div.pornInLang`).remove();
  320. }
  321. cleanup.body = function(){
  322. $(`#player`).removeClass(`original`);
  323. $(`#player`).addClass(`wide`);
  324. $(`#hd-rightColVideoPage`).removeClass(`original`);
  325. $(`#hd-rightColVideoPage`).addClass(`wide`);
  326. $(`div.mgp_controlBar > div.mgp_front > div.mgp_cinema`).remove();
  327. $(`#recommendedVideos`).attr('class','videos user-playlist allRecommendedVideos');
  328. $(`#recommendedVideosVPage > a`).remove();
  329. $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos`).addClass('allRelatedVideos');
  330. $(`#loadMoreRelatedVideosCenter`).remove();
  331. }
  332.  
  333. $("#profileMenuDropdownScroll > div.menuSection > .menuLink > a[href*='/feeds']").removeClass("halfWidth");
  334. $("#profileMenuDropdownScroll > div.menuSection > .menuLink > a[href*='/feeds'] > span").addClass("inline");
  335. $("#profileMenuDropdownScroll > div.menuSection > .menuLink > a[href*='/feeds'] > .iconHolder").remove();
  336. $(`<i class="ph-icon-rss-feed inline"></i>`).insertBefore("#profileMenuDropdownScroll > div.menuSection > .menuLink > a[href*='/feeds'] > span");
  337. $("#leftMenu a.menuLink[href*='https://help.pornhub.com/hc/en-us/categories/4419836212499']").removeClass("halfWidth");
  338. //
  339. var flags = JSON.parse(localStorage.getItem('flags')) || {"videos":[],"models":[]};
  340.  
  341. var favCategories = [
  342. { "name": "Uncategorized", "id": "0" },
  343. { "name": "General", "id": "1" }
  344. ];
  345.  
  346. // Global styles
  347. GM_addStyle(`
  348. /* https://sleazyfork.org/ru/scripts/368777-pornhub-crack-for-russia */
  349. #age-verification-container,#age-verification-wrapper,.abAlertShown,#js-abContainterMain{display:none!important}
  350.  
  351. @supports (display: grid) {
  352. html.supportsGridLayout #header #headerMenuContainer #headerMainMenuInner ul#headerMainMenu>li .wideDropdown.categories .innerDropdown {
  353. grid-template-columns: .75fr auto !important;
  354. }
  355. html.supportsGridLayout #header {
  356. grid-template-rows: auto !important;
  357. }
  358. }
  359.  
  360. #header #headerMenuContainer #headerMainMenuInner ul#headerMainMenu>li .wideDropdown.categories .middlePanel{
  361. width: 100%;
  362. }
  363.  
  364. .ph_dialog{
  365. margin:auto !important;
  366. background: #000;
  367. border-radius: 8px;
  368. min-width: 500px;
  369. max-width: 800px;
  370. border: 2px solid #ff9000;
  371. transition: all .5s ease-in-out;
  372. }
  373.  
  374. .ph_dialog > .heading{
  375. display: flex;
  376. align-content: center;
  377. padding: 10px;
  378. background: hsl(0,0%,10%);
  379. }
  380.  
  381. .ph_dialog > .heading > a{
  382. text-decoration: unset;
  383. color: #ff9000;
  384. }
  385.  
  386. .ph_dialog > .heading > *{
  387. display: flex;
  388. align-content: center;
  389. }
  390.  
  391. .ph_dialog > .heading > .left > .button,.ph_dialog > .heading > .right > .button{
  392. display: flex;
  393. justify-content: center;
  394. align-items: center;
  395. width: 24px;
  396. height: 24px;
  397. font-size: 16pt;
  398. padding: 0;
  399. margin: 0;
  400. }
  401.  
  402. .ph_dialog > .heading > .left{
  403. flex: 1;
  404. }
  405.  
  406. .ph_dialog > .heading > .right{
  407. margin-left: auto;
  408. }
  409.  
  410. .ph_dialog > .body{
  411. color: var(--ph__white);
  412. padding: 10px;
  413. overflow-y: auto;
  414. max-height: 800px;
  415. }
  416.  
  417. .ph_dialog > .body >*:not(:last-child){
  418. margin: 0 0 10px 0;
  419. }
  420.  
  421. .ph_comments__window > .ph_comments__container{
  422. padding: 10px;
  423. }
  424.  
  425. .ph_comments__window > .ph_comments__container .cmtHeader{
  426. padding: 1em !important;
  427. }
  428.  
  429. .ph_comments__window > .ph_comments__container #cmtContent{
  430. overflow-y: auto;
  431. max-height: 500px;
  432. }
  433.  
  434. .ph_recommended__window > .ph_recommended__container #recommendedVideos{
  435. overflow-y: auto;
  436. max-height: 500px;
  437. }
  438.  
  439. .ph_related__window > .ph_related__container #relatedVideosCenter{
  440. overflow-y: auto;
  441. max-height: 500px;
  442. }
  443.  
  444. .ph_modelVideos__window > .ph_modelVideos__container{
  445. overflow-y: auto;
  446. max-height: 500px;
  447. }
  448.  
  449. .ph_dialog::backdrop{
  450. z-index: 1;
  451. background: rgba(0,0,0,.8);
  452. }
  453.  
  454. .ph_video-actions button{
  455. padding: 4px 24px;
  456. display: inline-block;
  457. margin-bottom: 5px;
  458. border-radius: 8px;
  459. padding: 8px 18px;
  460. background: #1b1b1b;
  461. font-weight: 400;
  462. font-size: 14px;
  463. color: #fff;
  464. text-transform: capitalize;
  465. white-space: nowrap;
  466. border: 1px solid #ff9000;
  467. }
  468. .ph_video-actions button:hover{
  469. text-decoration: none;
  470. background-color: #2f2f2f;
  471. }
  472. .ph_videoShortcuts1{
  473. display: inline-flex;
  474. gap: 4px;
  475. }
  476. #ph_flag__msgbox{
  477. display: flex;
  478. gap: 8px;
  479. align-items: flex-start;
  480. padding: 8px;
  481. boder-radius: 8px;
  482. background: #ff9000;
  483. color: black;
  484. }
  485. #ph_flag__msgbox .icon{
  486. display: flex;
  487. flex: 0 0 32px;
  488. align-items: center;
  489. justify-content: center;
  490. height: 24px;
  491. width: 24px;
  492. font-size: 16px;
  493. }
  494. #ph_flag__msgbox .text{
  495. flex: 1;
  496. }
  497. .video-action-sub-tab.addToStream li .wrap .thumbnail-info-wrapper span.title, ul.videos li .wrap .thumbnail-info-wrapper span.title {
  498. max-height: min-content;
  499. }
  500. @media only screen and (min-width: 1350px){
  501. .video-action-sub-tab.addToStream li .wrap .thumbnail-info-wrapper span.title, ul.videos li .wrap .thumbnail-info-wrapper span.title {
  502. max-height: min-content;
  503. }
  504. }
  505. `);
  506.  
  507. GM_addStyle(GM_getResourceText("PH_CSS"));
  508.  
  509. $(`header`).append(`
  510. <link href="//cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@4/dark.css" rel="stylesheet">
  511. <script src="https://github.com/lofcz/sweetalert2-neutral/releases/download/v11.6.15-NEUTRAL/sweetalert2.all.min.js" defer></script>
  512. `);
  513.  
  514. $(`body`).append(`
  515. <script src="https://cdn.jsdelivr.net/gh/aolko/fontawesome-pro@master/fontawesome-pro-6.1.2-web/js/all.min.js" data-auto-replace-svg="nest"></script>
  516. <script src="https://cdn.jsdelivr.net/gh/aolko/fontawesome-pro@master/fontawesome-pro-6.1.2-web/js/v4-shims.min.js"></script>
  517. `);
  518.  
  519. console.info("[PH Toolbox] Cleaning up the page...");
  520. cleanup.page();
  521. cleanup.header();
  522. cleanup.body();
  523.  
  524. console.info("[PH Toolbox] Guessing your location...");
  525.  
  526. // LOCAL: specific pages
  527.  
  528. if(window.location.href.indexOf("/video/search") > -1 || window.location.href.indexOf("/transgender") > -1) {
  529. // PH search page
  530. console.info("[PH Toolbox] You are currently searching for a video");
  531. if(GM_config.get("HideShortVids") === true){
  532. var hiddenEl = 0;
  533. $(`ul.videos > .videoBox`).each(function(){
  534. var videoDuration = $(`.linkVideoThumb > .marker-overlays > var.duration`,this).text();
  535. if(timeToSeconds(videoDuration) < GM_config.get("ShortVidMin")){
  536. $(this).hide();
  537. hiddenEl++;
  538. }
  539. });
  540. console.log(`[PH Toolbox] Hidden ${hiddenEl} videos shorter than ${secondsToTime(GM_config.get("ShortVidMin"))}`);
  541. }
  542. }
  543. else if(window.location.href.indexOf("/view_video.php") > -1) {
  544. // PH video page
  545. console.info("[PH Toolbox] You are currently watching a video");
  546. var modelURL = $(`#hd-leftColVideoPage > div:nth-child(1) > div.video-actions-container > div.video-actions-tabs > div.video-action-tab.about-tab.active > div.video-detailed-info > div.video-info-row.userRow > div.userInfo > div > span > a`).attr('href');
  547.  
  548. $(`#hd-leftColVideoPage > div:nth-child(1) > div.video-actions-container > div.video-actions-tabs > div.video-action-tab.about-tab.active > div.video-detailed-info > div.video-info-row.userRow > div.userInfo > div.usernameWrap > span.usernameBadgesWrapper`).append(`
  549. <div class="ph_quickActions" style="display: flex; align-items: center; gap: 5px; margin: 0 5px 0 0;">
  550. <a href="${modelURL}/photos" data-popup><i class="fa-solid fa-images-user"></i> images</a>
  551. <a href="${modelURL}/videos" data-popup><i class="fa-solid fa-film"></i> videos</a>
  552. </div>
  553. `);
  554.  
  555. $(`#ph_loadVids`).click(function(){
  556. var modelURL = $(`#hd-leftColVideoPage > div:nth-child(1) > div.video-actions-container > div.video-actions-tabs > div.video-action-tab.about-tab.active > div.video-detailed-info > div.video-info-row.userRow > div.userInfo > div > span > a`).attr('href');
  557. showVids(modelURL);
  558. document.querySelector(`.ph_modelVideos__window`).showModal();
  559. });
  560.  
  561. $(`#relatedVideosCenter > .pcVideoListItem`).each(function(){
  562. var related_modelURL = $(`.wrap > .thumbnail-info-wrapper > .videoUploaderBlock > .usernameWrap > a`,this).attr('href');
  563. var related_vidURL = $(`.wrap > .thumbnail-info-wrapper > .title > a`,this).attr('href');
  564. $(`.wrap > .thumbnail-info-wrapper > .title`,this).append(`
  565. <div class="ph_quickActions">
  566. <a href="https://9xbud.com/https://www.pornhub.com/${related_vidURL}" class="tooltipTrig" data-title="Download"><i class="fa-solid fa-download"></i></a>
  567. </div>
  568. `);
  569. });
  570.  
  571. $(`#recommendedVideos > .pcVideoListItem`).each(function(){
  572. var recommended_modelURL = $(`.wrap > .thumbnail-info-wrapper > .videoUploaderBlock > .usernameWrap > a`,this).attr('href');
  573. var recommended_vidURL = $(`.wrap > .thumbnail-info-wrapper > .title > a`,this).attr('href');
  574. $(`.wrap > .thumbnail-info-wrapper > .title`,this).append(`
  575. <div class="ph_quickActions">
  576. <a href="https://9xbud.com/https://www.pornhub.com/${recommended_vidURL}" class="tooltipTrig" data-title="Download"><i class="fa-solid fa-download"></i></a>
  577. </div>
  578. `);
  579. });
  580.  
  581. $(`body`).append(`
  582. <dialog class="ph_dialog ph_comments__window">
  583. <div class="heading">
  584. <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Comments</h2></div>
  585. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  586. </div>
  587. <div class="ph_comments__container"></div>
  588. </dialog>
  589. `);
  590.  
  591. $(`body`).append(`
  592. <dialog class="ph_dialog ph_recommended__window">
  593. <div class="heading">
  594. <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Recommended videos</h2></div>
  595. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  596. </div>
  597. <div class="ph_recommended__container"></div>
  598. </dialog>
  599. `);
  600.  
  601. $(`body`).append(`
  602. <dialog class="ph_dialog ph_related__window">
  603. <div class="heading">
  604. <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Related videos</h2></div>
  605. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  606. </div>
  607. <div class="ph_related__container"></div>
  608. </dialog>
  609. `);
  610.  
  611. function isFlagged(){
  612. var cur_url = ph__url.href;
  613. cur_url = cur_url.replace(/#+$/, ''); // Remove # character from the end of the URL
  614. if(localStorage.flags){
  615. var flags = JSON.parse(localStorage.flags);
  616. // check against current url
  617. var video = $.grep(flags.videos, function(v) {
  618. return v.url === cur_url;
  619. })[0];
  620. if (video) {
  621. return {
  622. flagged: true,
  623. reason: video.reason,
  624. url: video.url
  625. };
  626. }
  627. }
  628. return { flagged: false };
  629. }
  630. $(`body`).append(`
  631. <dialog class="ph_dialog ph_flagVideo__window">
  632. <div class="heading">
  633. <div class="left"><h2><i class="fa-solid fa-flag"></i> Flag video</h2></div>
  634. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  635. </div>
  636. <div class="body">
  637. <div style="display:flex; gap: 4px;">
  638. <select id="ph_videoFlagReason" style="flex:1;">
  639. <option value="scam">Scam</option>
  640. <option value="ad">Ad</option>
  641. <option value="low_quality">Low quality</option>
  642. <option value="no_finisher">No orgasm/cumshot/creampie/climax</option>
  643. <option value="teaser">Teaser</option>
  644. <option value="wrong_tags">Wrong tags</option>
  645. <option value="wrong_category">Wrong category</option>
  646. </select>
  647. <button id="ph_flagVideo_btn">Flag video</button>
  648. </div>
  649. <div style="display:flex; gap: 4px;">
  650. <span style="flex:1;font-weight:600;" id="ph_flagVideo_status"></span>
  651. <button id="ph_flagVideo_clear_btn">Clear flags</button>
  652. </div>
  653. </div>
  654. </dialog>
  655. `);
  656. $(`<div id="ph_flag__msgbox"><span class="icon"><i class="fa-regular fa-flag"></i></span><span class="text"></span></div>`).insertBefore(`#player`);
  657. if(isFlagged().flagged){
  658. $(`#ph_flagVideo_status`).html(`<i class="fa-solid fa-warning fa-fw"></i> This video was flagged as "${isFlagged().reason}"`);
  659. $(`#ph_videoFlagReason`).prop("disabled",true);
  660. $(`#ph_flagVideo_btn`).prop("disabled",true);
  661. match(isFlagged().reason, {
  662. "scam||ad||low_quality": function() {
  663. $(`#ph_flag__msgbox>.text`).text(`
  664. ${getRandomMessage(dict__messages.video.malicious)}
  665. `);
  666. },
  667. "no_finisher||teaser": function() {
  668. $(`#ph_flag__msgbox>.text`).text(`
  669. ${getRandomMessage(dict__messages.video.tease)}
  670. `);
  671. },
  672. "wrong_tags||wrong_category": function(){
  673. $(`#ph_flag__msgbox>.text`).text(`
  674. ${getRandomMessage(dict__messages.video.wrong_classification)}
  675. `);
  676. }
  677. });
  678. } else {
  679. $(`#ph_flag__msgbox`).remove();
  680. $(`#ph_flagVideo_status`).text(`This video wasn't flagged`);
  681. }
  682.  
  683. $(`#under-player-comments`).appendTo(`.ph_comments__container`);
  684.  
  685. $(`#hd-leftColVideoPage > div:nth-child(1) > div.video-actions-container > div.video-actions-tabs > div.video-action-tab.about-tab.active > div.video-detailed-info > div.video-info-row.userRow > div.userActions`).append(`
  686. <div class="ph_commentsButton videoSubscribeButton">
  687. <button type="button">
  688. <i class="buttonIcon"></i>
  689. <span class="buttonLabel">Comments</span>
  690. </button>
  691. </div>
  692. `);
  693.  
  694. $(`#hd-leftColVideoPage > div:nth-child(1) > div.title-container.translate > h1`).append(`<div class="ph_videoShortcuts1"></div>`);
  695. $(`#hd-leftColVideoPage > div:nth-child(1) > div.title-container.translate > h1 > .ph_videoShortcuts1`).append(`<a id="ph_incognitoTab" href="#"><i class="fa-solid fa-user-secret"></i></a>`);
  696. $(`#hd-leftColVideoPage > div:nth-child(1) > div.title-container.translate > h1 > .ph_videoShortcuts1`).append(`<a id="ph_flag_btn" href="#"><i class="fa-solid fa-flag"></i></a>`);
  697.  
  698. $(`#ph_incognitoTab`).click(function(){
  699. GM_openInTab(window.location.href, {"incognito":true});
  700. });
  701.  
  702. $(`.ph_commentsButton>button`).click(function(){
  703. document.querySelector(`.ph_comments__window`).showModal();
  704. });
  705.  
  706. $(`<div class="video-wrapper ph_video-actions" style="margin:20px 0; padding: 10px;"></div>`).insertAfter(`#hd-leftColVideoPage > div:nth-child(1)`);
  707. $(`.ph_video-actions`).html(`
  708. <button id="ph_videoOnly_btn">Basic layout</button>
  709. <button id="ph_videoDownload_btn">Video download</button>
  710. `);
  711.  
  712. if(GM_config.get("Layout") === "Basic"){
  713. $(`#ph_videoOnly_btn`).hide();
  714. $(`.ph_video-actions`).append(`
  715. <button id="ph_relatedVids_btn">Related</button>
  716. <button id="ph_recommendedVids_btn">Recommended</button>
  717. `);
  718.  
  719. $(`#ph_recommendedVids_btn`).click(function(){
  720. document.querySelector(`.ph_recommended__window`).showModal();
  721. });
  722.  
  723. $(`#ph_relatedVids_btn`).click(function(){
  724. document.querySelector(`.ph_related__window`).showModal();
  725. });
  726.  
  727. $(`#ph_flag_btn`).click(function(){
  728. document.querySelector(`.ph_flagVideo__window`).showModal();
  729. });
  730.  
  731. $(`#recommendedVideos`).appendTo(`.ph_recommended__container`);
  732. $(`#relatedVideosCenter`).appendTo(`.ph_related__container`);
  733. $(`#hd-rightColVideoPage`).hide();
  734. $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos.allRelatedVideos`).hide();
  735. $(`#under-player-playlists`).hide();
  736.  
  737. GM_addStyle(`
  738. html.supportsGridLayout.fluidContainer #main-container #vpContentContainer:not(.premiumLocked) {
  739. grid-template-columns: 1fr;
  740. }
  741. `);
  742. } else {
  743.  
  744. }
  745.  
  746. $(`#ph_flagVideo_btn`).click(function(){
  747. var url = ph__url.toString();
  748. var videoFlag = {"url":url.replace("#", ""), "reason":$(`#ph_videoFlagReason`).val()}
  749. flags.videos.push(videoFlag);
  750. localStorage.setItem('flags', JSON.stringify(flags));
  751.  
  752. document.querySelector(`.ph_flagVideo__window`).close();
  753. Swal.fire({
  754. icon: 'success',
  755. title: 'Flagged',
  756. text: `Video was flagged with the reason "${$(`#ph_videoFlagReason`).val()}"`,
  757. }).then((result) => {
  758. if (result.isConfirmed) {
  759. document.location.reload(true);
  760. }
  761. });
  762. });
  763.  
  764. $(`#ph_videoOnly_btn`).click(function(){
  765. $(`.ph_video-actions`).append(`
  766. <button id="ph_relatedVids_btn">Related</button>
  767. <button id="ph_recommendedVids_btn">Recommended</button>
  768. `);
  769.  
  770. $(`#ph_recommendedVids_btn`).click(function(){
  771. document.querySelector(`.ph_recommended__window`).showModal();
  772. });
  773.  
  774. $(`#ph_relatedVids_btn`).click(function(){
  775. document.querySelector(`.ph_related__window`).showModal();
  776. });
  777.  
  778. $(`#recommendedVideos`).appendTo(`.ph_recommended__container`);
  779. $(`#relatedVideosCenter`).appendTo(`.ph_related__container`);
  780. $(`#hd-rightColVideoPage`).hide();
  781. $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos.allRelatedVideos`).hide();
  782. $(`#under-player-playlists`).hide();
  783.  
  784. GM_addStyle(`
  785. html.supportsGridLayout.fluidContainer #main-container #vpContentContainer:not(.premiumLocked) {
  786. grid-template-columns: 1fr;
  787. }
  788. `);
  789. });
  790.  
  791.  
  792. $(`#ph_videoDownload_btn`).click(function(){
  793. var url = window.location;
  794. var newUrl = window.location.href.replace(/(https?:\/\/)/, "$19xbud.com/");
  795. window.location = newUrl;
  796. });
  797.  
  798. $(`.allActionsContainer .CTAs #ctaBox`).append(`
  799. <li><span style="display:inline-block;width:100%;height:5px;"></span></li>
  800. <li><a href="#"><span>Favorite</span></a></li>
  801. `);
  802.  
  803. automator.onElement("#player",function(){
  804. if (automator.hasText(this, "No valid sources are available for this video.")) {
  805. Swal.fire("PH Toolbox","Your player went stale. Refresh the page.","info");
  806. }
  807. })
  808. }
  809. else if(window.location.href.indexOf("/model/") > -1
  810. || window.location.href.indexOf("/pornstar/") > -1
  811. || window.location.href.indexOf("/channels/") > -1
  812. || window.location.href.indexOf("/users/") > -1) {
  813. // PH profile
  814. console.info("[PH Toolbox] You are currently watching a model/channel/user page");
  815.  
  816. // Get the cache from local storage
  817. var cache = JSON.parse(localStorage.getItem('favCache')) || {};
  818.  
  819. // Function to cache the response for a request
  820. function cacheResponse(url, response) {
  821. cache[url] = response;
  822. localStorage.setItem('favCache', JSON.stringify(cache));
  823. }
  824.  
  825. // Favorites check
  826. function isFavorite(url){
  827. if(localStorage.favorites){
  828. // get the favorites value from the localstorage
  829. var _fav = JSON.parse(localStorage.favorites);
  830. // check against current url
  831. if($.inArray(url, _fav.profiles) !== -1){
  832. return true;
  833. }
  834. }
  835. return false;
  836. }
  837.  
  838. function isFlagged(){
  839. var cur_url = ph__url.href;
  840. cur_url = cur_url.replace(/#+$/, ''); // Remove # character from the end of the URL
  841. if(localStorage.flags){
  842. var flags = JSON.parse(localStorage.flags);
  843. // check against current url
  844. var model = $.grep(flags.models, function(m) {
  845. return m.url === cur_url;
  846. })[0];
  847. if (model) {
  848. return {
  849. flagged: true,
  850. reason: model.reason,
  851. url: model.url
  852. };
  853. }
  854. }
  855. return { flagged: false };
  856. }
  857.  
  858. if(isFavorite(ph__url.href)){
  859. $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
  860. <div class="float-left mainButton">
  861. <button id="ph_unfav_btn" class="buttonBase" type="button">
  862. <i class="fa-regular fa-star fa-fw" style="background:unset;"></i>
  863. <span class="buttonLabel">Unfavorite</span>
  864. </button>
  865. </div>
  866. `);
  867. } else {
  868. $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
  869. <div class="float-left mainButton">
  870. <button id="ph_fav_btn" class="buttonBase" type="button">
  871. <i class="fa-solid fa-star fa-fw" style="background:unset;"></i>
  872. <span class="buttonLabel">Favorite</span>
  873. </button>
  874. </div>
  875. `);
  876. };
  877. $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
  878. <div class="float-left mainButton">
  879. <button id="ph_flag_btn" class="buttonBase" type="button">
  880. <i class="fa-solid fa-flag fa-fw" style="background:unset;"></i>
  881. <span class="buttonLabel">Flag</span>
  882. </button>
  883. </div>
  884. `);
  885.  
  886. $(`#ph_fav_btn`).click(function(e){
  887. //e.preventDefault();
  888. var info = {};
  889. if (localStorage.favorites){
  890. var favorites = JSON.parse(localStorage.favorites);
  891. if (favorites.profiles.length === 0){
  892. var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
  893. localStorage.favorites = JSON.stringify(_fav);
  894. } else {
  895. favorites.profiles.push(ph__url);
  896. localStorage.favorites = JSON.stringify(favorites);
  897.  
  898. info = {
  899. "name":$(".topProfileHeader > div.coverImage div.name > h1").text().trim(),
  900. "avatar":$(".topProfileHeader > #avatarPicture #getAvatar").attr("src")
  901. };
  902.  
  903. // Add the favorite item to the cache
  904. cacheResponse(ph__url, {"name": info.name, "avatar": info.avatar});
  905.  
  906. Swal.fire({
  907. icon: 'success',
  908. title: 'Added to favorites',
  909. text: 'Model was added to favorites',
  910. }).then((result) => {
  911. if (result.isConfirmed) {
  912. document.location.reload(true);
  913. }
  914. });
  915. }
  916. } else {
  917. console.log("[PH toolbox] no localstorage");
  918. var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
  919. localStorage.favorites = JSON.stringify(_fav);
  920. }
  921. });
  922. $(`#ph_unfav_btn`).click(function(e){
  923. //e.preventDefault();
  924. if (localStorage.favorites){
  925. var favorites = JSON.parse(localStorage.favorites);
  926. if (favorites.profiles.length === 0){
  927. var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
  928. localStorage.favorites = JSON.stringify(_fav);
  929. } else {
  930. favorites.profiles = favorites.profiles.filter(url => url !== ph__url.href);
  931. localStorage.favorites = JSON.stringify(favorites);
  932.  
  933. // Remove the favorite item from the cache
  934. delete cache[ph__url];
  935. localStorage.setItem('favCache', JSON.stringify(cache));
  936.  
  937. Swal.fire({
  938. icon: 'success',
  939. title: 'Removed from favorites',
  940. text: 'Model was removed from favorites',
  941. }).then((result) => {
  942. if (result.isConfirmed) {
  943. document.location.reload(true);
  944. }
  945. });
  946. }
  947. } else {
  948. console.log("[PH toolbox] no localstorage");
  949. var _fav = {"profiles":[],"vids":[],"playlists":[]};
  950. localStorage.favorites = JSON.stringify(_fav);
  951. }
  952. });
  953.  
  954. if(GM_config.get("HideShortVids") === true){
  955. var hiddenEl = 0;
  956. $(`ul.videos > .videoBox`).each(function(){
  957. var videoDuration = $(`.linkVideoThumb > .marker-overlays > var.duration`,this).text();
  958. if(timeToSeconds(videoDuration) < GM_config.get("ShortVidMin")){
  959. $(this).hide();
  960. hiddenEl++;
  961. }
  962. });
  963. console.log(`[PH Toolbox] Hidden ${hiddenEl} videos shorter than ${secondsToTime(GM_config.get("ShortVidMin"))}`);
  964. }
  965.  
  966. $(`<div id="ph_flag__msgbox"><span class="icon"><i class="fa-regular fa-flag"></i></span><span class="text"></span></div>`).insertAfter(`#svgProfileElements`);
  967.  
  968. $(`body`).append(`
  969. <dialog class="ph_dialog ph_flagModel__window">
  970. <div class="heading">
  971. <div class="left"><h2><i class="fa-solid fa-flag"></i> Flag model</h2></div>
  972. <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
  973. </div>
  974. <div class="body">
  975. <div style="display:flex; gap: 4px;">
  976. <select id="ph_modelFlagReason" style="flex:1;">
  977. <optgroup label="Malicious activity">
  978. <option value="scam">Scam</option>
  979. <option value="ad">Ads</option>
  980. <option value="premium">Premium account</option>
  981. </optgroup>
  982. <optgroup label="Pleasure denial">
  983. <option value="no_finisher">Showoff/poser</option>
  984. <option value="teaser">Teaser</option>
  985. <option value="turnoff">Turn-off</option>
  986. </optgroup>
  987. <optgroup label="Misclassification">
  988. <option value="dead_profile">Dead profile</option>
  989. <option value="fake_profile">Fake profile</option>
  990. <option value="wrong_gender">Wrong gender</option>
  991. </optgroup>
  992. </select>
  993. <button id="ph_flagModel_btn">Flag model</button>
  994. </div>
  995. <div style="display:flex; gap: 4px;">
  996. <span style="flex:1;" id="ph_flagModel_status">This model wasn't flagged</span>
  997. <button id="ph_flagModel_clear_btn">Clear flags</button>
  998. </div>
  999. </div>
  1000. </dialog>
  1001. `);
  1002.  
  1003. if(isFlagged().flagged){
  1004. $(`#ph_flagModel_status`).html(`<i class="fa-solid fa-warning fa-fw"></i> This profile was flagged as "${isFlagged().reason}"`);
  1005. $(`#ph_modelFlagReason`).prop("disabled",true);
  1006. $(`#ph_flagModel_btn`).prop("disabled",true);
  1007. match(isFlagged().reason, {
  1008. "scam||ad||premium": function() {
  1009. $(`#ph_flag__msgbox>.text`).text(`
  1010. ${getRandomMessage(dict__messages.profile.malicious)}
  1011. `);
  1012. },
  1013. "no_finisher||teaser||turnoff": function() {
  1014. $(`#ph_flag__msgbox>.text`).text(`
  1015. ${getRandomMessage(dict__messages.profile.tease)}
  1016. `);
  1017. },
  1018. "dead_profile||fake_profile": function(){
  1019. $(`#ph_flag__msgbox>.text`).text(`
  1020. ${getRandomMessage(dict__messages.profile.inactive_profile)}
  1021. `);
  1022. },
  1023. "wrong_gender": function(){
  1024. $(`#ph_flag__msgbox>.text`).text(`
  1025. ${getRandomMessage(dict__messages.profile.wrong_classification)}
  1026. `);
  1027. }
  1028. });
  1029. } else {
  1030. $(`#ph_flag__msgbox`).remove();
  1031. $(`#ph_flagModel_status`).text(`This profile wasn't flagged`);
  1032. }
  1033.  
  1034. $(`#ph_flag_btn`).click(function(){
  1035. document.querySelector(`.ph_flagModel__window`).showModal();
  1036. });
  1037.  
  1038. $(`#ph_flagModel_btn`).click(function(){
  1039. var url = ph__url.toString();
  1040. var modelFlag = {"url":url.replace("#", ""), "reason":$(`#ph_modelFlagReason`).val()}
  1041. flags.models.push(modelFlag);
  1042. localStorage.setItem('flags', JSON.stringify(flags));
  1043.  
  1044. document.querySelector(`.ph_flagModel__window`).close();
  1045. Swal.fire({
  1046. icon: 'success',
  1047. title: 'Flagged',
  1048. text: `Profile was flagged with the reason "${$(`#ph_modelFlagReason`).val()}"`,
  1049. }).then((result) => {
  1050. if (result.isConfirmed) {
  1051. document.location.reload(true);
  1052. }
  1053. });
  1054. });
  1055.  
  1056.  
  1057. }
  1058. else if(window.location.href.indexOf("/playlist/") > -1) {
  1059. // PH playlist
  1060. console.info("[PH Toolbox] You are currently watching a playlist");
  1061. }
  1062. else if(window.location.href.indexOf("/user/friend_requests") > -1){
  1063.  
  1064. $(`
  1065. <button id="ph_massFriendSpam" class="orangeButton clearfix" style="margin: 10px 0;" type="button">Mark all as spam</button>
  1066. `).insertBefore(`.showingInfo.vgf`)
  1067. var spamBtnClicks = 0;
  1068.  
  1069. $(`#ph_massFriendSpam`).click(function(){
  1070. $.wait = function(ms) {
  1071. var defer = $.Deferred();
  1072. setTimeout(function() { defer.resolve(); }, ms);
  1073. return defer;
  1074. };
  1075. spamBtnClicks++;
  1076. if ($("#profileFeaturedVideo > .sectionWrapper > #moreData").children().length) {
  1077. var _el = $("#profileFeaturedVideo > .sectionWrapper > #moreData");
  1078. console.warn("[PH Toolbox] Mass marking friend requests as spam...");
  1079. $(`li.requestBox`,_el).each(function(){
  1080. //$(this).css("background","red");
  1081. $("a.spam",this).trigger("click");
  1082. automator.click("#modalWrapMTubes > div > div > div.modal-body > div:nth-child(3) > button.spriteProfileIcons.orangeButton.bigButton.yesBtn");
  1083. });
  1084. } else {
  1085. console.info("[PH Toolbox] Can't do anything, chief. No friend requests.");
  1086. if(spamBtnClicks < 3){
  1087. } else if(spamBtnClicks > 3 && spamBtnClicks < 5){
  1088. Swal.fire('Hi, buddy','Calm down please, you have no friend requests right now.','info');
  1089. } else if(spamBtnClicks > 10 && spamBtnClicks < 30){
  1090. Swal.fire('Hey','I said calm down.','warning');
  1091. } else if(spamBtnClicks > 30){
  1092. Swal.fire({
  1093. title: 'HEY!!!',
  1094. html: 'STOP. CLICKING. THE. BUTTON.',
  1095. icon: 'error',
  1096. timer: 10000,
  1097. timerProgressBar: true,
  1098. allowEscapeKey: false,
  1099. allowEnterKey: false,
  1100. allowOutsideClick: false,
  1101. showConfirmButton: false,
  1102. });
  1103. $(`#ph_massFriendSpam`).prop('disabled', true);
  1104. $(`#ph_massFriendSpam`).addClass('disabled');
  1105. $.wait(15000).then(function(){
  1106. Swal.fire({
  1107. title: 'Haha',
  1108. html: 'I have disabled the button. Who\'s laughing now?',
  1109. timer: 10000,
  1110. timerProgressBar: true,
  1111. allowEscapeKey: false,
  1112. allowEnterKey: false,
  1113. allowOutsideClick: false,
  1114. showConfirmButton: false,
  1115. });
  1116. });
  1117. }
  1118. }
  1119. });
  1120. }
  1121. else{
  1122. // Other cases
  1123. }
  1124.  
  1125. // GLOBAL: All pages
  1126.  
  1127. $(`dialog.ph_dialog #ph_modal-close`).click(function(){
  1128. $(this).closest(`.ph_dialog`)[0].close();
  1129. console.log($(this));
  1130. });
  1131.  
  1132. // Add profile shortcuts on pages with video thumbs
  1133. $(`div.thumbnail-info-wrapper.clearfix > div.videoUploaderBlock.clearfix`).each(function(){
  1134. var modelUrl_list = $(`.usernameWrap a`,this).attr('href');
  1135. $(this).append(`
  1136. <div class="ph_quickActions" style="display: inline-flex; align-items: center; gap: 5px; margin: 0 5px 0 0;">
  1137. <a href="${modelUrl_list}/photos" data-popup><i class="fa-solid fa-images-user"></i> images</a>
  1138. <a href="${modelUrl_list}/videos" data-popup><i class="fa-solid fa-film"></i> videos</a>
  1139. </div>
  1140. `);
  1141. });
  1142.  
  1143. $(`
  1144. <div class="menuLink">
  1145. <a href="/toolbox" class="halfWidth">
  1146. <i class="fa-solid fa-toolbox fa-fw inline" style="color:var(--ph__brand--primary);"></i>
  1147. <span class="inline" style="color:var(--ph__brand--primary);">PH Toolbox</span>
  1148. </a>
  1149. <a href="/toolbox/favorites" class="halfWidth">
  1150. <i class="fa-solid fa-heart fa-fw inline" style="color:var(--ph__brand--primary);"></i>
  1151. <span class="inline" style="color:var(--ph__brand--primary);">[PH Toolbox] Favorites</span>
  1152. </a>
  1153. </div>
  1154. `).insertAfter(`#profileMenuDropdownScroll > div.menuSection > .menuLink:has(a[href*='/translate/sign_up'])`);
  1155.  
  1156.  
  1157. // Userscript pages
  1158. if(window.location.href.indexOf("/toolbox") > -1) {
  1159. console.info("[PH Toolbox] Page: Toolbox");
  1160. $(`head>title`).text(`PH toolbox`);
  1161. $(`.wrapper > .container`).html(`
  1162. <div class="sectionWrapper">
  1163. <div class="section" style="padding: 20px;">
  1164. <h1>PH Toolbox</h1>
  1165. <h2>Links</h2>
  1166. <p>
  1167. <ul>
  1168. <li><a href="/toolbox/favorites/">My favorites</a></li>
  1169. </ul>
  1170. </p>
  1171. </div>
  1172. </div>
  1173. `);
  1174. if(window.location.href.indexOf("/favorites") > -1) {
  1175.  
  1176. var favorites = JSON.parse(localStorage.favorites);
  1177. console.info("[PH Toolbox] Page: Toolbox » Favorites");
  1178. $(`head>title`).text(`PH toolbox: Favorites`);
  1179. $(`head`).append(`
  1180. <link rel="stylesheet" href="https://di.phncdn.com/www-static/css/./pornstar-beforeaction-pc.css?cache=2022121401" type="text/css" />
  1181. <link rel="stylesheet" href="https://di.phncdn.com/www-static/css/./pornstar-pornstars-pc.css?cache=2022121401" type="text/css" />
  1182. `);
  1183. $(`.wrapper > .container`).addClass(`ph__root`);
  1184. $(`.wrapper > .container`).html(`
  1185. <div class="sectionWrapper">
  1186. <div class="section" style="padding: 20px;">
  1187. <a id="ph_getFavorites" href="#" class="buttonBase orangeButton big">Get favorites</a>
  1188. </div>
  1189. </div>
  1190. <div class="sectionWrapper">
  1191. <div class="section" style="padding: 20px;">
  1192. <a href="/toolbox"><h1>PH Toolbox</h1></a>
  1193. <h2>Favorites</h2>
  1194. <p><i class="fa-regular fa-warning"></i> If your images or text appear to be broken or missing - wait a few moments and refresh the page.</p>
  1195. </div>
  1196. </div>
  1197. <div class="pornstar_container">
  1198. <div class="sectionWrapper">
  1199. <!-- Model list -->
  1200. <ul id="ph_favModels" class="videos row-5-thumbs popular-pornstar">
  1201. </ul>
  1202. <!-- / -->
  1203. </div>
  1204. </div>
  1205. `);
  1206.  
  1207. $(`#ph_favModels`).append(`<div class="ph__loader">Loading the favorites...</div>`);
  1208. //$(`.ph__loader`).show();
  1209.  
  1210. // Set the rate limit in milliseconds
  1211. var rateLimit = 5000;
  1212.  
  1213. // Set the maximum number of concurrent promises
  1214. var maxConcurrentPromises = 2;
  1215.  
  1216. var promises = [];
  1217.  
  1218. function jsonInfo(url){
  1219. var Url = new URL(url);
  1220. var relUrl = Url.pathname + Url.search;
  1221. return new Promise(function(resolve, reject) {
  1222. var info = {};
  1223. $.ajax({
  1224. type: "GET",
  1225. url: relUrl,
  1226. success: function (response) {
  1227. info = {
  1228. "name":$(".topProfileHeader > div.coverImage div.name > h1", response).text().trim(),
  1229. "avatar":$(".topProfileHeader > #avatarPicture #getAvatar", response).attr("src")
  1230. };
  1231. resolve(info);
  1232. },
  1233. error: function(jqxhr, status, exception) {
  1234. // Group the error message in the console
  1235. console.groupCollapsed(`Error: ${exception.message}`);
  1236. //console.error(exception);
  1237. //alert('Exception:', exception);
  1238. reject(exception);
  1239. console.groupEnd();
  1240. }
  1241. });
  1242. });
  1243. }
  1244.  
  1245. // Get the cache from local storage
  1246. var cache = JSON.parse(localStorage.getItem('favCache')) || {};
  1247.  
  1248. // Function to check if a request is cached
  1249. function isCached(url) {
  1250. return cache.hasOwnProperty(url);
  1251. }
  1252.  
  1253. // Function to get the cached response for a request
  1254. function getCachedResponse(url) {
  1255. return cache[url];
  1256. }
  1257.  
  1258. // Function to cache the response for a request
  1259. function cacheResponse(url, response) {
  1260. cache[url] = response;
  1261. localStorage.setItem('favCache', JSON.stringify(cache));
  1262. }
  1263.  
  1264. // Create a function to process the promises
  1265. function processPromises() {
  1266. // Check if there are more promises in the array
  1267. if (promises.length > 0) {
  1268. // Get the next batch of promises
  1269. const batch = promises.splice(0, maxConcurrentPromises);
  1270.  
  1271. // Wait for the batch to complete
  1272. Promise.all(batch)
  1273. .then(function() {
  1274. // Process the next batch of promises
  1275. setTimeout(processPromises(),rateLimit);
  1276. })
  1277. .catch(function(error) {
  1278. // Group the error message in the console
  1279. console.groupCollapsed(`Error: ${error.message}`);
  1280. console.error(error);
  1281. console.groupEnd();
  1282.  
  1283. // Process the next batch of promises
  1284. setTimeout(processPromises(),rateLimit);
  1285. });
  1286. } else {
  1287. // Hide the loader element
  1288. $(`.ph__loader`).hide();
  1289. }
  1290. }
  1291. function sendRequests() {
  1292. // Initialize a flag to track if all items are cached
  1293. var allCached = true;
  1294.  
  1295. $.each(favorites.profiles, function(index, profileUrl) {
  1296. // Set the retry count to 0
  1297. var retryCount = 0;
  1298. var info = jsonInfo(profileUrl);
  1299. // Function to send the request and handle the response
  1300. function sendRequest() {
  1301. // Check if the request is already cached
  1302. if (isCached(profileUrl)) {
  1303. // Get the cached response
  1304. var cachedResponse = getCachedResponse(profileUrl);
  1305.  
  1306. // Check if the cached response is valid
  1307. var isValid = true;
  1308. for (var prop in cachedResponse) {
  1309. /*if (!cachedResponse.hasOwnProperty(prop) && !cachedResponse[prop]) {
  1310. isValid = false;
  1311. break;
  1312. }*/
  1313. if (cachedResponse.hasOwnProperty(prop) && cachedResponse[prop]) {
  1314. // prop is not empty, so continue to the next iteration
  1315. continue;
  1316. } else {
  1317. // prop is empty or does not exist, so set isValid to false
  1318. isValid = false;
  1319. break;
  1320. }
  1321. }
  1322.  
  1323. if (isValid) {
  1324. // Do something with the cached response
  1325. var htmlItem = `
  1326. <li class="modelLi">
  1327. <div class="wrap">
  1328. <a href="${profileUrl}">
  1329. <span class="pornstar_label">
  1330. <span class="title-album">
  1331. <span>Model</span>
  1332. </span>
  1333. </span>
  1334. <img class="lazy"
  1335. src="${cachedResponse.avatar}"
  1336. alt="${cachedResponse.name}">
  1337. </a>
  1338. <div class="thumbnail-info-wrapper">
  1339. <a href="${profileUrl}" class="title">
  1340. <span class="modelName">${cachedResponse.name}</span>
  1341. </a>
  1342. </div>
  1343. </div>
  1344. </li>
  1345. `;
  1346. // Append the HTML to the DOM and fade it in
  1347. $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
  1348. console.log(cachedResponse);
  1349. } else {
  1350. // Set the allCached flag to false
  1351. allCached = false;
  1352.  
  1353. // Add the request promise to the array
  1354. promises.push(
  1355. jsonInfo(profileUrl)
  1356. .then(function(info) {
  1357. // Cache the response
  1358. cacheResponse(profileUrl, info);
  1359.  
  1360. // Do something with the response
  1361. //console.log(info);
  1362. var htmlItem = `
  1363. <li class="modelLi">
  1364. <div class="wrap">
  1365. <a href="${profileUrl}">
  1366. <span class="pornstar_label">
  1367. <span class="title-album">
  1368. <span>Model</span>
  1369. </span>
  1370. </span>
  1371. <img class="lazy"
  1372. src="${info.avatar}"
  1373. alt="${info.name}">
  1374. </a>
  1375. <div class="thumbnail-info-wrapper">
  1376. <a href="${profileUrl}" class="title">
  1377. <span class="modelName">${info.name}</span>
  1378. </a>
  1379. </div>
  1380. </div>
  1381. </li>
  1382. `;
  1383. // Append the HTML to the DOM and fade it in
  1384. $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
  1385. console.log(info);
  1386. })
  1387. .catch(function(error) {
  1388. // Increment the retry count
  1389. retryCount++;
  1390.  
  1391. // If the retry count is less than the maximum number of retries, retry the request
  1392. if (retryCount < 5) {
  1393. console.warn(`[PH Toolbox] Can't get the model info, retrying... (${retryCount}/5)`)
  1394. sendRequest();
  1395. } else {
  1396. // Group the error message in the console
  1397. console.groupCollapsed(`[PH toolbox] Error: ${error.message}`);
  1398. console.error(error);
  1399. console.groupEnd();
  1400. }
  1401. })
  1402. );
  1403. }
  1404. } else {
  1405. // Set the allCached flag to false
  1406. allCached = false;
  1407.  
  1408. // Add the request promise to the array
  1409. promises.push(
  1410. jsonInfo(profileUrl)
  1411. .then(function(info) {
  1412. // Cache the response
  1413. cacheResponse(profileUrl, info);
  1414.  
  1415. // Do something with the response
  1416. //console.log(info);
  1417. var htmlItem = `
  1418. <li class="modelLi">
  1419. <div class="wrap">
  1420. <a href="${profileUrl}">
  1421. <span class="pornstar_label">
  1422. <span class="title-album">
  1423. <span>Model</span>
  1424. </span>
  1425. </span>
  1426. <img class="lazy"
  1427. src="${info.avatar}"
  1428. alt="${info.name}">
  1429. </a>
  1430. <div class="thumbnail-info-wrapper">
  1431. <a href="${profileUrl}" class="title">
  1432. <span class="modelName">${info.name}</span>
  1433. </a>
  1434. </div>
  1435. </div>
  1436. </li>
  1437. `;
  1438. // Append the HTML to the DOM and fade it in
  1439. $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
  1440. console.log(info);
  1441. })
  1442. .catch(function(error) {
  1443. // Increment the retry count
  1444. retryCount++;
  1445.  
  1446. // If the retry count is less than the maximum number of retries, retry the request
  1447. if (retryCount < 5) {
  1448. console.warn(`[PH Toolbox] Can't get the model info, retrying... (${retryCount}/5)`)
  1449. sendRequest();
  1450. } else {
  1451. // Group the error message in the console
  1452. console.groupCollapsed(`[PH toolbox] Error: ${error.message}`);
  1453. console.error(error);
  1454. console.groupEnd();
  1455. }
  1456. })
  1457. );
  1458. }
  1459. }
  1460.  
  1461. // Send the request in the background
  1462. setTimeout(sendRequest, 0);
  1463. });
  1464.  
  1465. // Check if all items are cached
  1466. if (allCached) {
  1467. // Hide the loader element
  1468. $(`.ph__loader`).hide();
  1469. } else {
  1470. // Process the promises
  1471. processPromises();
  1472. }
  1473. };
  1474.  
  1475. $(`#ph_getFavorites`).click(function(){
  1476. Swal.fire({
  1477. toast: true,
  1478. position: 'top-end',
  1479. showConfirmButton: false,
  1480. showCloseButton: false,
  1481. title: "Getting favorites",
  1482. timer: 5000,
  1483. timerProgressBar: true
  1484. });
  1485. if($(`#ph_favModels`).length){
  1486. $(`#ph_favModels`).html("");
  1487. }
  1488. setTimeout(sendRequests,rateLimit);
  1489.  
  1490. // Wait for all the promises to complete
  1491. setTimeout(processPromises(),rateLimit);
  1492. });
  1493. }
  1494. }
  1495.  
  1496. //---
  1497.  
  1498. $(`body > div.footerContentWrapper > h2`).html(`Running scripts`);
  1499. $(`body > div.footerContentWrapper > p`).html(`
  1500. <p><i class="fa-solid fa-puzzle-piece-simple"></i> <span>PH Toolbox</span>,
  1501. <i class="fa-solid fa-puzzle-piece-simple"></i> <span>pornhub crack for Russia</span>
  1502. `);
  1503. });