PH toolbox

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

2022-12-21 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         PH toolbox
// @namespace    http://tampermonkey.net/
// @version      0.6.0-pre6
// @description  PornHub toolbox (https://codeberg.org/aolko/userscripts)
// @author       aolko
// @license      GPL-3.0-or-later
// @match        *://*.pornhub.com/*
// @match        *pornhub.com*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pornhub.com
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @require      https://code.jquery.com/jquery-3.6.1.min.js
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// ==/UserScript==

/*
Check the official repo: https://codeberg.org/aolko/userscripts
*/

/*! instant.page v5.1.1 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
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)}

/* globals $ */
var $ = window.jQuery;

var frame = document.createElement('div');
document.body.appendChild(frame);

GM_config.init(
{
  'id': 'ph__config', // The id used for this instance of GM_config
  'title': "⚙ PH toolbox settings",
  'fields': // Fields object
  {
    'Layout': // This is the id of the field
    {
      'label': 'Layout',
      'type': 'radio',
      'options': ['Default','Basic'],
      'default': 'Default'
    }
  },
  'events':
  {
    'open': function(){
      GM_config.frame.setAttribute('style', `
        position: fixed; /* Stay in place */
        display: flex !important;
        flex-direction: column;
        align-items: center;
        z-index: 9999; /* Sit on top */
        left: 0;
        top: 0;
        width: 100%; /* Full width */
        height: 100%; /* Full height */
        overflow: auto; /* Enable scroll if needed */
      `);
    }
  },
  'frame': frame,
  'css': `
  #ph__config{background: hsl(0 0% 0% / .8);}
  #ph__config_wrapper{
    position: relative;
    top: 20%;
    margin:auto !important;
    background: #000;
    border-radius: 8px;
    max-width: 500px;
    border: 2px solid #ff9000;
    transition: all .5s ease-in-out;
  }
  #ph__config_header,#ph__config_Layout_var{padding: 10px;}
  `
});

if (typeof GM_registerMenuCommand === 'function') {
    GM_registerMenuCommand('Settings', function() {
        GM_config.open();
    });
}

function getElementByXpath(path) {
  return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function showVids(url){
  var full_url = ph__domain + url+"/videos";
  console.log(full_url);
  $(`body`).append(`
             <dialog class="ph_dialog ph_modelVideos__window">
                <div class="heading">
                    <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Model's videos</h2></div>
                    <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
                </div>
                <div class="ph_modelVideos__container"></div>
             </dialog>
         `)
  //$( "dialog.phmodelVideos__window" ).load( "${full_url}/videos #videosTab > div > div > div:nth-child(2)" );
  $.ajax({
    url:full_url,
    type: 'GET',
    cache: false,
    success: function(data){
      $('dialog.ph_modelVideos__window > .ph_modelVideos__container').html($(data).find('#videosTab > .sectionWrapper > .profileVids').html());
    }
  });
};

function parseURL(url){
  var getQueryParams = function (query) {
    var params = {};
    new URLSearchParams(query).forEach(function (value, key) {
      var decodedKey = decodeURIComponent(key);
      var decodedValue = decodeURIComponent(value);
      // This key is part of an array
      if (decodedKey.endsWith("[]")) {
        decodedKey = decodedKey.replace("[]", "");
        params[decodedKey] || (params[decodedKey] = []);
        params[decodedKey].push(decodedValue);
        // Just a regular parameter
      } else {
        params[decodedKey] = decodedValue;
      }
    });

    return params;
  };

  var _url = new URL(url);
  _url._path = _url.pathname.split( '/' ).join(`#/#`).split(`#`);
  _url._path[0] = _url.origin;
  _url._params = getQueryParams(_url.searchParams);
  return _url;
}

const ph__domain = window.location.origin;

// global namespace for placing cleanup functions in
var cleanup = cleanup || {};

const ph__url = parseURL(window.location.href);
(function() {
  'use strict';

  console.log(`[PH toolbox] Started`);
  console.log(GM_listValues());

  console.log(ph__url);

  cleanup.page = function(){
    $(`#age-verification-container,#age-verification-wrapper,.abAlertShown,#js-abContainterMain`).remove();
  }
  cleanup.header = function(){
    $(`#js-networkBar`).remove();
    $(`#menuItem4.livesex`).remove();
    $(`#menuItem5.pornstar`).remove();
    $(`#menuItem6`).remove();
    $(`#menuItem7.community`).remove();
    $(`#menuItem8.photos`).remove();
    $(`#countryRedirectMessage`).remove();
    $(`#welcome`).remove();
    $(`body > div.wrapper > div.container > div.frontListingWrapper > div:nth-child(1) > div`).remove();
    $(`#profileMenuDropdown > li.upgradeToPremiumCustom`).remove();
    $(`#profileMenuDropdown > li.orders`).remove();
    $(`#profileMenuDropdown > li.feed > a`).attr('href','/feeds?section=videos');
    $(`#coummunityMenuItems > li:nth-child(3)`).remove();
    $(getElementByXpath(`/html/body/div[4]/div[3]/div[5]/div[2]/comment()[3]`)).next().remove();

    $(`#menuItem2 > div > div > div.leftPanel.videos > ul > li:nth-child(10)`).remove();
    $(`#dropdownHeaderSubMenu > div.innerHeaderSubMenu.trendingWrapper`).remove();
    $(`#menuItem3 > div > div > div.rightPanel`).remove();
    $(`#menuItem3 > div > div > div.leftPanel > div.pornInLang`).remove();
  }
  cleanup.body = function(){
    $(`#player`).removeClass(`original`);
    $(`#player`).addClass(`wide`);
    $(`#hd-rightColVideoPage`).removeClass(`original`);
    $(`#hd-rightColVideoPage`).addClass(`wide`);
    $(`div.mgp_controlBar > div.mgp_front > div.mgp_cinema`).remove();
    $(`#recommendedVideos`).attr('class','videos user-playlist allRecommendedVideos');
    $(`#recommendedVideosVPage > a`).remove();
    $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos`).addClass('allRelatedVideos');
    $(`#loadMoreRelatedVideosCenter`).remove();
  }

  // Global styles
  GM_addStyle(`
        /* https://sleazyfork.org/ru/scripts/368777-pornhub-crack-for-russia */
        #age-verification-container,#age-verification-wrapper,.abAlertShown,#js-abContainterMain{display:none!important}

        @supports (display: grid) {
            html.supportsGridLayout #header #headerMenuContainer #headerMainMenuInner ul#headerMainMenu>li .wideDropdown.categories .innerDropdown {
                grid-template-columns: .75fr auto !important;
            }
        }
        #header #headerMenuContainer #headerMainMenuInner ul#headerMainMenu>li .wideDropdown.categories .middlePanel{
            width: 100%;
        }

        .ph_dialog{
            margin:auto !important;
            background: #000;
            border-radius: 8px;
            max-width: 800px;
            border: 2px solid #ff9000;
            transition: all .5s ease-in-out;
        }

        .ph_dialog > .heading{
            display: flex;
            align-content: center;
            padding: 10px;
            background: hsl(0,0%,10%);
        }

        .ph_dialog > .heading > a{
            text-decoration: unset;
            color: #ff9000;
        }

        .ph_dialog > .heading > *{
            display: flex;
            align-content: center;
        }

        .ph_dialog > .heading > .left > .button,.ph_dialog > .heading > .right > .button{
            display: flex;
            justify-content: center;
            align-items: center;
            width: 24px;
            height: 24px;
            font-size: 16pt;
            padding: 0;
            margin: 0;
        }

        .ph_dialog > .heading > .left{
            flex: 1;
        }

        .ph_dialog > .heading > .right{
            margin-left: auto;
        }

        .ph_comments__window > .ph_comments__container{
            padding: 10px;
        }

        .ph_comments__window > .ph_comments__container .cmtHeader{
            padding: 1em !important;
        }

        .ph_comments__window > .ph_comments__container #cmtContent{
            overflow-y: auto;
            max-height: 500px;
        }

        .ph_recommended__window > .ph_recommended__container #recommendedVideos{
            overflow-y: auto;
            max-height: 500px;
        }

        .ph_related__window > .ph_related__container #relatedVideosCenter{
            overflow-y: auto;
            max-height: 500px;
        }

        .ph_modelVideos__window > .ph_modelVideos__container{
            overflow-y: auto;
            max-height: 500px;
        }

        .ph_dialog::backdrop{
            z-index: 1;
            background: rgba(0,0,0,.8);
        }

        .ph_video-actions button{
            padding: 4px 24px;
            display: inline-block;
            margin-bottom: 5px;
            border-radius: 8px;
            padding: 8px 18px;
            background: #1b1b1b;
            font-weight: 400;
            font-size: 14px;
            color: #fff;
            text-transform: capitalize;
            white-space: nowrap;
            border: 1px solid #ff9000;
        }
        .ph_video-actions button:hover{
            text-decoration: none;
            background-color: #2f2f2f;
        }
        .video-action-sub-tab.addToStream li .wrap .thumbnail-info-wrapper span.title, ul.videos li .wrap .thumbnail-info-wrapper span.title {
            max-height: min-content;
        }
        @media only screen and (min-width: 1350px){
            .video-action-sub-tab.addToStream li .wrap .thumbnail-info-wrapper span.title, ul.videos li .wrap .thumbnail-info-wrapper span.title {
                max-height: min-content;
            }
        }
    `);

  $(`header`).append(`
  <link href="//cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@4/dark.css" rel="stylesheet">
  <script src="https://github.com/lofcz/sweetalert2-neutral/releases/download/v11.6.15-NEUTRAL/sweetalert2.all.min.js" defer></script>
  `);

  $(`body`).append(`
        <script src="https://cdn.jsdelivr.net/gh/aolko/fontawesome-pro@master/fontawesome-pro-6.1.2-web/js/all.min.js"></script>
        <script src="https://cdn.jsdelivr.net/gh/aolko/fontawesome-pro@master/fontawesome-pro-6.1.2-web/js/v4-shims.min.js"></script>
    `);

  console.info("[PH Toolbox] Cleaning up the page...");
  cleanup.page();
  cleanup.header();
  cleanup.body();

  console.info("[PH Toolbox] Guessing your location...");

  // LOCAL: specific pages

  if(window.location.href.indexOf("/video/search") > -1 || window.location.href.indexOf("/transgender") > -1) {
    // PH search page
    console.info("[PH Toolbox] You are currently searching for a video");
  }
  else if(window.location.href.indexOf("/view_video.php") > -1) {
    // PH video page
    console.info("[PH Toolbox] You are currently watching a video");
    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');

    $(`#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(`
            <div class="ph_quickActions" style="display: flex; align-items: center; gap: 5px; margin: 0 5px 0 0;">
                <a href="${modelURL}/photos" data-popup><i class="fa-solid fa-images-user"></i> images</a>
                <a href="${modelURL}/videos" data-popup><i class="fa-solid fa-film"></i> videos</a>
            </div>
        `);

    $(`#ph_loadVids`).click(function(){
      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');
      showVids(modelURL);
      document.querySelector(`.ph_modelVideos__window`).showModal();
    });

    $(`#relatedVideosCenter > .pcVideoListItem`).each(function(){
      var related_modelURL = $(`.wrap > .thumbnail-info-wrapper > .videoUploaderBlock > .usernameWrap > a`,this).attr('href');
      var related_vidURL = $(`.wrap > .thumbnail-info-wrapper > .title > a`).attr('href');
      $(`.wrap > .thumbnail-info-wrapper > .title`,this).append(`
                <div class="ph_quickActions">
                    <a href="https://armagan-easydl.herokuapp.com/?q=${encodeURIComponent("https://pornhub.com"+related_vidURL)}" class="tooltipTrig" data-title="Download"><i class="fa-solid fa-download"></i></a>
                </div>
            `);
    });

    $(`#recommendedVideos > .pcVideoListItem`).each(function(){
      var recommended_modelURL = $(`.wrap > .thumbnail-info-wrapper > .videoUploaderBlock > .usernameWrap > a`,this).attr('href');
      var recommended_vidURL = $(`.wrap > .thumbnail-info-wrapper > .title > a`).attr('href');
      $(`.wrap > .thumbnail-info-wrapper > .title`,this).append(`
                <div class="ph_quickActions">
                    <a href="https://armagan-easydl.herokuapp.com/?q=${encodeURIComponent("https://pornhub.com"+recommended_vidURL)}" class="tooltipTrig" data-title="Download"><i class="fa-solid fa-download"></i></a>
                </div>
            `);
    });

    $(`body`).append(`
            <dialog class="ph_dialog ph_comments__window">
                <div class="heading">
                    <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Comments</h2></div>
                    <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
                </div>
                <div class="ph_comments__container"></div>
            </dialog>
        `);

    $(`body`).append(`
            <dialog class="ph_dialog ph_recommended__window">
                <div class="heading">
                    <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Recommended videos</h2></div>
                    <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
                </div>
                <div class="ph_recommended__container"></div>
            </dialog>
        `);

    $(`body`).append(`
            <dialog class="ph_dialog ph_related__window">
                <div class="heading">
                    <div class="left"><h2><i class="fa-solid fa-puzzle-piece-simple"></i> Related videos</h2></div>
                    <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
                </div>
                <div class="ph_related__container"></div>
            </dialog>
        `);

    $(`#under-player-comments`).appendTo(`.ph_comments__container`);

    $(`#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(`
            <div class="ph_commentsButton videoSubscribeButton">
                <button type="button">
                    <i class="buttonIcon"></i>
                    <span class="buttonLabel">Comments</span>
                </button>
            </div>
        `);

    $(`#hd-leftColVideoPage > div:nth-child(1) > div.title-container.translate > h1`).append(`<a id="ph_incognitoTab" href="#"><i class="fa-solid fa-user-secret"></i></a>`);

    $(`#ph_incognitoTab`).click(function(){
      GM_openInTab(window.location.href, {"incognito":true});
    });

    $(`.ph_commentsButton>button`).click(function(){
      document.querySelector(`.ph_comments__window`).showModal();
    });

    $(`<div class="video-wrapper ph_video-actions" style="margin:20px 0; padding: 10px;"></div>`).insertAfter(`#hd-leftColVideoPage > div:nth-child(1)`);
    $(`.ph_video-actions`).html(`
            <button id="ph_videoOnly_btn">Basic layout</button>
            <button id="ph_videoDownload_btn">Video download</button>
        `);

    if(GM_config.get("Layout") === "Basic"){
      $(`#ph_videoOnly_btn`).hide();
      $(`.ph_video-actions`).append(`
            <button id="ph_relatedVids_btn">Related</button>
            <button id="ph_recommendedVids_btn">Recommended</button>
        `);

      $(`#ph_recommendedVids_btn`).click(function(){
        document.querySelector(`.ph_recommended__window`).showModal();
      });

      $(`#ph_relatedVids_btn`).click(function(){
        document.querySelector(`.ph_related__window`).showModal();
      });

      $(`#recommendedVideos`).appendTo(`.ph_recommended__container`);
      $(`#relatedVideosCenter`).appendTo(`.ph_related__container`);
      $(`#hd-rightColVideoPage`).hide();
      $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos.allRelatedVideos`).hide();
      $(`#under-player-playlists`).hide();

      GM_addStyle(`
        html.supportsGridLayout.fluidContainer #main-container #vpContentContainer:not(.premiumLocked) {
            grid-template-columns: 1fr;
        }
      `);
    } else {

    }
    $(`#ph_videoOnly_btn`).click(function(){
      $(`.ph_video-actions`).append(`
            <button id="ph_relatedVids_btn">Related</button>
            <button id="ph_recommendedVids_btn">Recommended</button>
        `);

      $(`#ph_recommendedVids_btn`).click(function(){
        document.querySelector(`.ph_recommended__window`).showModal();
      });

      $(`#ph_relatedVids_btn`).click(function(){
        document.querySelector(`.ph_related__window`).showModal();
      });

      $(`#recommendedVideos`).appendTo(`.ph_recommended__container`);
      $(`#relatedVideosCenter`).appendTo(`.ph_related__container`);
      $(`#hd-rightColVideoPage`).hide();
      $(`#hd-leftColVideoPage > div.video-wrapper.js-relatedRecommended.js-relatedVideos.relatedVideos.allRelatedVideos`).hide();
      $(`#under-player-playlists`).hide();

      GM_addStyle(`
            html.supportsGridLayout.fluidContainer #main-container #vpContentContainer:not(.premiumLocked) {
                grid-template-columns: 1fr;
            }
        `);
    });

    $(`dialog.ph_dialog #ph_modal-close`).click(function(){
      $(this).closest(`.ph_dialog`)[0].close();
      console.log($(this));
    });


    // https://greasyfork.org/en/scripts/447477-armagan-s-easydl
    $(`#ph_videoDownload_btn`).click(function(){
      var url = window.location;
      window.location = `https://armagan-easydl.herokuapp.com/?q=${encodeURIComponent(url)}`
        });

    $(`.allActionsContainer .CTAs #ctaBox`).append(`
      <li><span style="display:inline-block;width:100%;height:5px;"></span></li>
      <li><a href="#"><span>Favorite</span></a></li>
    `);
  }
  else if(window.location.href.indexOf("/model/") > -1 || window.location.href.indexOf("/channels/") > -1 || window.location.href.indexOf("/users/") > -1) {
    // PH profile
    console.info("[PH Toolbox] You are currently watching a model/channel/user page");

    // Get the cache from local storage
    var cache = JSON.parse(localStorage.getItem('favCache')) || {};

    // Function to cache the response for a request
    function cacheResponse(url, response) {
      cache[url] = response;
      localStorage.setItem('favCache', JSON.stringify(cache));
    }

    // Favorites check
    function isFavorite(url){
      if(localStorage.favorites){
        // get the favorites value from the localstorage
        var _fav = JSON.parse(localStorage.favorites);
        // check against current url
        if($.inArray(url, _fav.profiles) !== -1){
          return true;
        }
      }
      return false;
    }

    if(isFavorite(ph__url.href)){
      $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
                <div class="float-left mainButton">
                    <button id="ph_unfav_btn" class="buttonBase" type="button">
                        <i class="fa-regular fa-star fa-fw"></i>
                        <span class="buttonLabel">Unfavorite</span>
                    </button>
                </div>
            `);
    } else {
      $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
                <div class="float-left mainButton">
                    <button id="ph_fav_btn" class="buttonBase" type="button">
                        <i class="fa-solid fa-star fa-fw"></i>
                        <span class="buttonLabel">Favorite</span>
                    </button>
                </div>
            `);
    };
    $(`#mainMenuProfile > div.userButtonsMenu > div.userButtons`).append(`
      <div class="float-left mainButton">
          <button id="ph_flag_btn" class="buttonBase" type="button">
              <i class="fa-solid fa-flag fa-fw"></i>
              <span class="buttonLabel">Flag</span>
          </button>
      </div>
    `);

    $(`#ph_fav_btn`).click(function(e){
      //e.preventDefault();
      var info = {};
      if (localStorage.favorites){
        var favorites = JSON.parse(localStorage.favorites);
        if (favorites.profiles.length === 0){
          var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
          localStorage.favorites = JSON.stringify(_fav);
        } else {
          favorites.profiles.push(ph__url);
          localStorage.favorites = JSON.stringify(favorites);

          info = {
            "name":$(".topProfileHeader > div.coverImage div.name > h1").text().trim(),
            "avatar":$(".topProfileHeader > #avatarPicture #getAvatar").attr("src")
          };

          // Add the favorite item to the cache
          cacheResponse(ph__url, {"name": info.name, "avatar": info.avatar});

          Swal.fire({
            icon: 'success',
            title: 'Added to favorites',
            text: 'Model was added to favorites',
          }).then((result) => {
            if (result.isConfirmed) {
              document.location.reload(true);
            }
          });
        }
      } else {
        console.log("[PH toolbox] no localstorage");
        var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
        localStorage.favorites = JSON.stringify(_fav);
      }
    });
    $(`#ph_unfav_btn`).click(function(e){
      //e.preventDefault();
      if (localStorage.favorites){
        var favorites = JSON.parse(localStorage.favorites);
        if (favorites.profiles.length === 0){
          var _fav = {"profiles":[ph__url],"vids":[],"playlists":[]};
          localStorage.favorites = JSON.stringify(_fav);
        } else {
          favorites.profiles = favorites.profiles.filter(url => url !== ph__url.href);
          localStorage.favorites = JSON.stringify(favorites);

          // Remove the favorite item from the cache
          delete cache[ph__url];
          localStorage.setItem('favCache', JSON.stringify(cache));

          Swal.fire({
            icon: 'success',
            title: 'Removed from favorites',
            text: 'Model was removed from favorites',
          }).then((result) => {
            if (result.isConfirmed) {
              document.location.reload(true);
            }
          });
        }
      } else {
        console.log("[PH toolbox] no localstorage");
        var _fav = {"profiles":[],"vids":[],"playlists":[]};
        localStorage.favorites = JSON.stringify(_fav);
      }
    });



  }
  else if(window.location.href.indexOf("/playlist/") > -1) {
    // PH playlist
    console.info("[PH Toolbox] You are currently watching a playlist");
  }
  else{
    // Other cases
  }

  // GLOBAL: All pages
  // Add profile shortcuts on pages with video thumbs
  $(`div.thumbnail-info-wrapper.clearfix > div.videoUploaderBlock.clearfix`).each(function(){
    var modelUrl_list = $(`.usernameWrap a`,this).attr('href');
    $(this).append(`
            <div class="ph_quickActions" style="display: inline-flex; align-items: center; gap: 5px; margin: 0 5px 0 0;">
                <a href="${modelUrl_list}/photos" data-popup><i class="fa-solid fa-images-user"></i> images</a>
                <a href="${modelUrl_list}/videos" data-popup><i class="fa-solid fa-film"></i> videos</a>
            </div>
        `);
  });


  // Userscript pages
  if(window.location.href.indexOf("/toolbox") > -1) {
    console.info("[PH Toolbox] Page: Toolbox");
    $(`head>title`).text(`PH toolbox`);
    $(`.wrapper > .container`).html(`
      <div class="sectionWrapper">
        <div class="section" style="padding: 20px;">
          <h1>PH Toolbox</h1>
          <h2>Links</h2>
          <p>
            <ul>
              <li><a href="/toolbox/favorites/">My favorites</a></li>
            </ul>
          </p>
        </div>
      </div>
    `);
    if(window.location.href.indexOf("/favorites") > -1) {

      var favorites = JSON.parse(localStorage.favorites);
      console.info("[PH Toolbox] Page: Toolbox » Favorites");
      $(`head>title`).text(`PH toolbox: Favorites`);
      $(`head`).append(`
        <link rel="stylesheet" href="https://di.phncdn.com/www-static/css/./pornstar-beforeaction-pc.css?cache=2022121401" type="text/css" />
        <link rel="stylesheet" href="https://di.phncdn.com/www-static/css/./pornstar-pornstars-pc.css?cache=2022121401" type="text/css" />
      `);
      $(`.wrapper > .container`).html(`
        <div class="sectionWrapper">
          <div class="section" style="padding: 20px;">
            <a id="ph_getFavorites" href="#" class="buttonBase orangeButton big">Get favorites</a>
          </div>
        </div>
        <div class="sectionWrapper">
          <div class="section" style="padding: 20px;">
            <a href="/toolbox"><h1>PH Toolbox</h1></a>
            <h2>Favorites</h2>
            <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>
          </div>
        </div>
        <div class="pornstar_container">
          <div class="sectionWrapper">
            <!-- Model list -->
            <ul id="ph_favModels" class="videos row-5-thumbs popular-pornstar">
            </ul>
            <!-- / -->
          </div>
        </div>
      `);

      $(`#ph_favModels`).append(`<div class="ph__loader">Loading the favorites...</div>`);
      //$(`.ph__loader`).show();

      // Set the rate limit in milliseconds
      var rateLimit = 5000;

      // Set the maximum number of concurrent promises
      var maxConcurrentPromises = 2;

      var promises = [];

      function jsonInfo(url){
        var Url = new URL(url);
        var relUrl = Url.pathname + Url.search;
        return new Promise(function(resolve, reject) {
          var info = {};
          $.ajax({
            type: "GET",
            url: relUrl,
            success: function (response) {
              info = {
                "name":$(".topProfileHeader > div.coverImage div.name > h1", response).text().trim(),
                "avatar":$(".topProfileHeader > #avatarPicture #getAvatar", response).attr("src")
              };
              resolve(info);
            },
            error: function(jqxhr, status, exception) {
                // Group the error message in the console
                console.groupCollapsed(`Error: ${exception.message}`);
                //console.error(exception);
                //alert('Exception:', exception);
                reject(exception);
                console.groupEnd();
            }
          });
        });
      }

      // Get the cache from local storage
      var cache = JSON.parse(localStorage.getItem('favCache')) || {};

      // Function to check if a request is cached
      function isCached(url) {
        return cache.hasOwnProperty(url);
      }

      // Function to get the cached response for a request
      function getCachedResponse(url) {
        return cache[url];
      }

      // Function to cache the response for a request
      function cacheResponse(url, response) {
        cache[url] = response;
        localStorage.setItem('favCache', JSON.stringify(cache));
      }

      // Create a function to process the promises
      function processPromises() {
        // Check if there are more promises in the array
        if (promises.length > 0) {
          // Get the next batch of promises
          const batch = promises.splice(0, maxConcurrentPromises);

          // Wait for the batch to complete
          Promise.all(batch)
            .then(function() {
              // Process the next batch of promises
              setTimeout(processPromises(),rateLimit);
            })
            .catch(function(error) {
              // Group the error message in the console
              console.groupCollapsed(`Error: ${error.message}`);
              console.error(error);
              console.groupEnd();

              // Process the next batch of promises
              setTimeout(processPromises(),rateLimit);
            });
        } else {
          // Hide the loader element
          $(`.ph__loader`).hide();
        }
      }
      
      function sendRequests() {
        $.each(favorites.profiles, function(index, profileUrl) {
          // Set the retry count to 0
          var retryCount = 0;
          var info = jsonInfo(profileUrl);
        
          // Function to send the request and handle the response
          function sendRequest() {
            // Check if the request is already cached
            if (isCached(profileUrl)) {
              // Get the cached response
              var cachedResponse = getCachedResponse(profileUrl);

              // Check if the cached response is valid
              var isValid = true;
              for (var prop in cachedResponse) {
                /*if (!cachedResponse.hasOwnProperty(prop) && !cachedResponse[prop]) {
                  isValid = false;
                  break;
                }*/
                if (cachedResponse.hasOwnProperty(prop) && cachedResponse[prop]) {
                  // prop is not empty, so continue to the next iteration
                  continue;
                } else {
                  // prop is empty or does not exist, so set isValid to false
                  isValid = false;
                  break;
                }
              }

              if (isValid) {
                // Do something with the cached response
                var htmlItem = `
                <li class="modelLi">
                  <div class="wrap">
                      <a href="${profileUrl}">
                          <span class="pornstar_label">
                              <span class="title-album">
                                  <span>Model</span>
                              </span>
                          </span>
                          <img class="lazy"
                              src="${cachedResponse.avatar}"
                              alt="${cachedResponse.name}">
                      </a>
                      <div class="thumbnail-info-wrapper">
                          <a href="${profileUrl}" class="title">
                              <span class="modelName">${cachedResponse.name}</span>
                          </a>
                      </div>
                  </div>
                </li>
                `;
                // Append the HTML to the DOM and fade it in
                $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
                
                console.log(cachedResponse);
              } else {
                // Add the request promise to the array
                promises.push(
                  jsonInfo(profileUrl)
                    .then(function(info) {
                      // Cache the response
                      cacheResponse(profileUrl, info);

                      // Do something with the response
                      //console.log(info);
                      var htmlItem = `
                      <li class="modelLi">
                        <div class="wrap">
                            <a href="${profileUrl}">
                                <span class="pornstar_label">
                                    <span class="title-album">
                                        <span>Model</span>
                                    </span>
                                </span>
                                <img class="lazy"
                                    src="${info.avatar}"
                                    alt="${info.name}">
                            </a>
                            <div class="thumbnail-info-wrapper">
                                <a href="${profileUrl}" class="title">
                                    <span class="modelName">${info.name}</span>
                                </a>
                            </div>
                        </div>
                      </li>
                      `;
                      // Append the HTML to the DOM and fade it in
                      $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
                      console.log(info);
                    })
                    .catch(function(error) {
                      // Increment the retry count
                      retryCount++;

                      // If the retry count is less than the maximum number of retries, retry the request
                      if (retryCount < 5) {
                        console.warn(`[PH Toolbox] Can't get the model info, retrying... (${retryCount}/5)`)
                        sendRequest();
                      } else {
                        // Group the error message in the console
                        console.groupCollapsed(`[PH toolbox] Error: ${error.message}`);
                        console.error(error);
                        console.groupEnd();
                      }
                    })
                );
              }
            } else {
              // Add the request promise to the array
              promises.push(
                jsonInfo(profileUrl)
                  .then(function(info) {
                    // Cache the response
                    cacheResponse(profileUrl, info);

                    // Do something with the response
                    //console.log(info);
                    var htmlItem = `
                    <li class="modelLi">
                      <div class="wrap">
                          <a href="${profileUrl}">
                              <span class="pornstar_label">
                                  <span class="title-album">
                                      <span>Model</span>
                                  </span>
                              </span>
                              <img class="lazy"
                                  src="${info.avatar}"
                                  alt="${info.name}">
                          </a>
                          <div class="thumbnail-info-wrapper">
                              <a href="${profileUrl}" class="title">
                                  <span class="modelName">${info.name}</span>
                              </a>
                          </div>
                      </div>
                    </li>
                    `;
                    // Append the HTML to the DOM and fade it in
                    $(`#ph_favModels`).append(htmlItem).find('.modelLi').fadeIn();
                    console.log(info);
                  })
                  .catch(function(error) {
                    // Increment the retry count
                    retryCount++;

                    // If the retry count is less than the maximum number of retries, retry the request
                    if (retryCount < 5) {
                      console.warn(`[PH Toolbox] Can't get the model info, retrying... (${retryCount}/5)`)
                      sendRequest();
                    } else {
                      // Group the error message in the console
                      console.groupCollapsed(`[PH toolbox] Error: ${error.message}`);
                      console.error(error);
                      console.groupEnd();
                    }
                  })
              );
            }
          }

          // Send the request in the background
          setTimeout(sendRequest, 0);
        });
      };

      $(`#ph_getFavorites`).click(function(){
        Swal.fire({
          toast: true,
          position: 'top-end',
          showConfirmButton: false,
          showCloseButton: false,
          title: "Getting favorites",
          timer: 5000,
          timerProgressBar: true
        });
        if($(`#ph_favModels`).length){
          $(`#ph_favModels`).html("");
        }
        
        setTimeout(sendRequests,rateLimit);

        // Wait for all the promises to complete
        setTimeout(processPromises(),rateLimit);
      });
    }
  }

  //---

  $(`body > div.footerContentWrapper > h2`).html(`Running scripts`);
  $(`body > div.footerContentWrapper > p`).html(`
        <p><i class="fa-solid fa-puzzle-piece-simple"></i> <span>PH Toolbox</span>,
        <i class="fa-solid fa-puzzle-piece-simple"></i> <span>pornhub crack for Russia</span>,
        <i class="fa-solid fa-puzzle-piece-simple"></i> <span>Armagan's EasyDL</span></p>
    `);
})();