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