PH toolbox

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

Pada tanggal 21 Desember 2022. Lihat %(latest_version_link).

// ==UserScript==
// @name         PH toolbox
// @namespace    http://tampermonkey.net/
// @version      0.6.0-pre7
// @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_getResourceText
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @resource     PH_CSS https://codeberg.org/aolko/userscripts/raw/branch/master/ph_toolbox/ph.style.css?v1
// @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'
    },
    'HideShortVids':
    {
      'label': 'Hide short videos', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': false // Default value if user doesn't change it
    },
    'ShortVidMin':
    {
      'label': 'Minimum video length (in seconds)', // Appears next to field
      'type': 'int', // Makes this setting a text input
      'min': 0, // Optional lower range limit
      'max': 600, // Optional upper range limit
      'default': 60 // Default value if user doesn't change it
    }
  },
  '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,.config_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;
}

function secondsToTime(e) {
  var h = Math.floor(e / 3600).toString().padStart(2, '0'),
      m = Math.floor(e % 3600 / 60).toString().padStart(2, '0'),
      s = Math.floor(e % 60).toString().padStart(2, '0');
  
  if (h > 0) {
    // Include the hours value in the output string if it is greater than 0
    return h + ':' + m + ':' + s;
  } else {
    // Omit the hours value if it is 0 or less
    return m + ':' + s;
  }
}


function timeToSeconds(e) {
  // Split the input time string into an array of hours, minutes, and seconds
  var [m, s] = e.split(":");
  
  // Convert the hours, minutes, and seconds to integers and return the total number of seconds
  return parseInt(m, 10) * 60 + parseInt(s, 10);
}


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);


  // TODO:
  // ✅ 1. group removals into functions
  // ✅ ___maybe even namespace those___
  // ✅ 2. group page-specific replacements by urls
  // ✅ 3. add favorites:
  // ✅ - for models
  // ✅ 3.1. make "My favorites" page, get the details (avatar, name) via ajax
  // 4. add (preset) tags for models/videos
  // or maybe notes that will be displayed above the video
  // 5. bookmarks? (jumpToAction(<seconds>))
  // 6. display messages/warnings based on:
  // - video name keywords/phrases (AND duration)
  // - gender descriptions?
  // - video tags
  // ✅ 7. user flagging (flag as...)
  /*
    {
      flags: {
        "videos": [
        {"url": "www.asdf.com",
        "reason": "1",
        "timecode": "00:00"}
        ],
        "models": [
        {"url": "www.asdf.com",
        "reason": "1"}
        ],
      }
    }
  */
  // - flag model as... (by model id/slug)
  //  - Spam account
  //  - Service ads
  //  - Premium
  //  - Dead account
  //  - Turn-off/unattractive
  //  - Fake identity
  // - flag video as... (by video id)
  //  - Wrong tags
  //  - Wrong category
  //  - Spam
  //  - Premium ad
  //  - Teaser
  //  - Low quality
  // ✅ 8. Hide short videos?
  // hide videos shorter than X minutes

  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();
  }

  var flags = JSON.parse(localStorage.getItem('flags')) || {"videos":[],"models":[]};

  // 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;
            min-width: 500px;
            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_dialog > .body{
            color: var(--ph__white);
            padding: 10px;
            overflow-y: auto;
            max-height: 800px;
        }

        .ph_dialog > .body >*:not(:last-child){
          margin: 0 0 10px 0;
        }

        .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;
        }
        .ph_videoShortcuts1{
          display: inline-flex;
          gap: 4px;
        }
        .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;
            }
        }
    `);

  GM_addStyle(GM_getResourceText("PH_CSS"));

  $(`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");
    if(GM_config.get("HideShortVids") === true){
      var hiddenEl = 0;
      $(`ul.videos > .videoBox`).each(function(){
        var videoDuration = $(`.linkVideoThumb > .marker-overlays > var.duration`,this).text();
        if(timeToSeconds(videoDuration) < GM_config.get("ShortVidMin")){
          $(this).hide();
          hiddenEl++;
        }
      });
      console.log(`[PH Toolbox] Hidden ${hiddenEl} videos shorter than ${secondsToTime(GM_config.get("ShortVidMin"))}`);
    }
  }
  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>
        `);

    function isFlagged(){
      var cur_url = ph__url.href;
      cur_url = cur_url.replace(/#+$/, ''); // Remove # character from the end of the URL
      if(localStorage.flags){
        var flags = JSON.parse(localStorage.flags);
        // check against current url
        var video = $.grep(flags.videos, function(v) {
          return v.url === cur_url;
        })[0];
        if (video) {
          return {
            flagged: true,
            reason: video.reason,
            url: video.url
          };
        }
      }
      return { flagged: false };
    }
             
    $(`body`).append(`
        <dialog class="ph_dialog ph_flagVideo__window">
            <div class="heading">
                <div class="left"><h2><i class="fa-solid fa-flag"></i> Flag video</h2></div>
                <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
            </div>
            <div class="body">
              <div style="display:flex; gap: 4px;">
                <select id="ph_videoFlagReason" style="flex:1;">
                  <option value="scam">Scam</option>
                  <option value="ad">Ad</option>
                  <option value="low_quality">Low quality</option>
                  <option value="no_finisher">No orgasm/cumshot/creampie/climax</option>
                  <option value="teaser">Teaser</option>
                  <option value="wrong_tags">Wrong tags</option>
                  <option value="wrong_category">Wrong category</option>
                </select>
                <button id="ph_flagVideo_btn">Flag video</button>
              </div>
              <div style="display:flex; gap: 4px;">
                <span style="flex:1;font-weight:600;" id="ph_flagVideo_status"></span>
                <button id="ph_flagVideo_clear_btn">Clear flags</button>
              </div>
            </div>
        </dialog>
    `);

    if(isFlagged().flagged){
      $(`#ph_flagVideo_status`).html(`<i class="fa-solid fa-warning fa-fw"></i> This video was flagged as "${isFlagged().reason}"`);
      $(`#ph_videoFlagReason`).prop("disabled",true);
      $(`#ph_flagVideo_btn`).prop("disabled",true);
    } else {
      $(`#ph_flagVideo_status`).text(`This video wasn't flagged`);
    }

    $(`#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(`<div class="ph_videoShortcuts1"></div>`);
    $(`#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>`);
    $(`#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>`);

    $(`#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();
      });

      $(`#ph_flag_btn`).click(function(){
        document.querySelector(`.ph_flagVideo__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_flagVideo_btn`).click(function(){
      var url = ph__url.toString();
      var videoFlag = {"url":url.replace("#", ""), "reason":$(`#ph_videoFlagReason`).val()}
      flags.videos.push(videoFlag);
      localStorage.setItem('flags', JSON.stringify(flags));

      document.querySelector(`.ph_flagVideo__window`).close();
      Swal.fire({
        icon: 'success',
        title: 'Flagged',
        text: `Video was flagged with the reason "${$(`#ph_videoFlagReason`).val()}"`,
      }).then((result) => {
        if (result.isConfirmed) {
          document.location.reload(true);
        }
      });
    });

    $(`#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;
            }
        `);
    });



    // 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;
    }

    function isFlagged(){
      var cur_url = ph__url.href;
      cur_url = cur_url.replace(/#+$/, ''); // Remove # character from the end of the URL
      if(localStorage.flags){
        var flags = JSON.parse(localStorage.flags);
        // check against current url
        var model = $.grep(flags.models, function(m) {
          return m.url === cur_url;
        })[0];
        if (model) {
          return {
            flagged: true,
            reason: model.reason,
            url: model.url
          };
        }
      }
      return { flagged: 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);
      }
    });

    if(GM_config.get("HideShortVids") === true){
      var hiddenEl = 0;
      $(`ul.videos > .videoBox`).each(function(){
        var videoDuration = $(`.linkVideoThumb > .marker-overlays > var.duration`,this).text();
        if(timeToSeconds(videoDuration) < GM_config.get("ShortVidMin")){
          $(this).hide();
          hiddenEl++;
        }
      });
      console.log(`[PH Toolbox] Hidden ${hiddenEl} videos shorter than ${secondsToTime(GM_config.get("ShortVidMin"))}`);
    }

    $(`body`).append(`
        <dialog class="ph_dialog ph_flagModel__window">
            <div class="heading">
                <div class="left"><h2><i class="fa-solid fa-flag"></i> Flag model</h2></div>
                <div class="right"><a id="ph_modal-close" class="button"><i class="fa-solid fa-times"></i></a></div>
            </div>
            <div class="body">
            <div style="display:flex; gap: 4px;">
              <select id="ph_modelFlagReason" style="flex:1;">
                <option value="scam">Scam</option>
                <option value="ad">Ads</option>
                <option value="premium">Premium account</option>
                <option value="no_finisher">Showoff/poser</option>
                <option value="teaser">Teaser</option>
                <option value="dead_profile">Dead profile</option>
                <option value="fake_profile">Fake profile</option>
                <option value="turnoff">Turn-off</option>
                <option value="wrong_gender">Wrong gender</option>
              </select>
              <button id="ph_flagModel_btn">Flag model</button>
              </div>
              <div style="display:flex; gap: 4px;">
                <span style="flex:1;" id="ph_flagModel_status">This model wasn't flagged</span>
                <button id="ph_flagModel_clear_btn">Clear flags</button>
              </div>
            </div>
        </dialog>
    `);

    if(isFlagged().flagged){
      $(`#ph_flagModel_status`).html(`<i class="fa-solid fa-warning fa-fw"></i> This profile was flagged as "${isFlagged().reason}"`);
      $(`#ph_modelFlagReason`).prop("disabled",true);
      $(`#ph_flagModel_btn`).prop("disabled",true);
    } else {
      $(`#ph_flagModel_status`).text(`This profile wasn't flagged`);
    }

    $(`#ph_flag_btn`).click(function(){
      document.querySelector(`.ph_flagModel__window`).showModal();
    });

    $(`#ph_flagModel_btn`).click(function(){
      var url = ph__url.toString();
      var modelFlag = {"url":url.replace("#", ""), "reason":$(`#ph_modelFlagReason`).val()}
      flags.models.push(modelFlag);
      localStorage.setItem('flags', JSON.stringify(flags));

      document.querySelector(`.ph_flagModel__window`).close();
      Swal.fire({
        icon: 'success',
        title: 'Flagged',
        text: `Profile was flagged with the reason "${$(`#ph_modelFlagReason`).val()}"`,
      }).then((result) => {
        if (result.isConfirmed) {
          document.location.reload(true);
        }
      });
    });


  }
  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

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

  // 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`).addClass(`ph__root`);
      $(`.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>
    `);
})();