// ==UserScript==
// @name PH - Search & UI Tweaks
// @namespace brazenvoid
// @version 1.2.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Various search filters and user experience enhancers
// @include https://www.pornhub.com/*
// @run-at document-end
// ==/UserScript==
// Settings & Defaults
let blacklistedWords = [ // case-insensitive
'urin',
'arab',
'muslim',
'desi',
'squirt',
'fake',
'pregnant',
'pee',
];
let videoNameSanitization = { // Substitutes values with key (case-insensitive)
' ' : ['neighbor', 'step'],
'Boyfriend': ['brother', 'bro', 'daddy', 'dad'],
'Girlfriend': ['daughter', 'mom', 'mother', 'sister', 'sis']
};
const defaultMinimumRating = 80;
const defaultMinimumDurationInSeconds = 120;
const hideNonHDVideosOnVideoPages = true;
const hidePrivateVideos = true;
const hideUnratedVideos = true;
const removeIFrames = true;
const removeLiveModelsSection = true;
const removePornStarsListingInSidebar = true;
const showUI = true;
const enableDebugLogging = false;
// Setting up configuration
let observerConfig = {
attributes: false,
childList: true,
subtree: false
};
// Pre-building regular expressions
for (let i = 0; i < blacklistedWords.length; i++) {
blacklistedWords[i] = new RegExp(blacklistedWords[i], 'ig');
}
for (const substitute in videoNameSanitization) {
for (let i = 0; i < videoNameSanitization[substitute].length; i++) {
videoNameSanitization[substitute][i] = new RegExp(videoNameSanitization[substitute][i], 'ig');
}
}
// Setting up logging
let log = function (message) {
if (enableDebugLogging) {
console.log(message);
}
};
let logAction = function (action) {
log('Completed: ' + action);
logSeparator();
};
let logValidationResult = function (filterName, statusVariable = null) {
log('Satisfies ' + filterName + ' Filter: ' + (statusVariable ? 'true' : 'false'));
};
let logSeparator = function () {
log('------------------------------------------------------------------------------------');
};
let logVideoName = function (videoName) {
log('Checking Video: ' + videoName);
};
// Blacklist validation
let validateBlacklist = function (videoItem, videoName) {
let validationCheck;
for (const blacklistedWord of blacklistedWords) {
validationCheck = videoName.match(blacklistedWord) === null;
if (!validationCheck) {
break;
}
}
logValidationResult('Blacklist', validationCheck);
return validationCheck;
};
// Duration validation
let validateDuration = function (videoItem, minDuration) {
let duration = videoItem.querySelector('.duration').textContent.split(':');
duration = (parseInt(duration[0]) * 60) + parseInt(duration[1]);
let validationCheck = duration >= minDuration;
logValidationResult('Duration', validationCheck);
return validationCheck;
};
// High definition validation
let validateHD = function (videoItem) {
if (hideNonHDVideosOnVideoPages) {
let validationCheck = videoItem.querySelector('.hd-thumbnail') !== null;
logValidationResult('HD', validationCheck);
return validationCheck;
}
return true;
};
// Private video validation
let validatePrivateVideo = function (videoItem) {
if (hidePrivateVideos) {
let validationCheck = videoItem.querySelector('.privateOverlay') === null;
logValidationResult('Private Video', validationCheck);
return validationCheck;
}
return true;
};
// Rating validation
let validateRating = function (videoItem, minRating) {
let rating = videoItem.querySelector('.value');
let validationCheck;
if (rating === null) {
validationCheck = !hideUnratedVideos;
} else {
rating = parseInt(rating.textContent.replace('%', ''));
validationCheck = rating >= minRating;
}
logValidationResult('Rating', validationCheck);
return validationCheck;
};
// Video name sanitization
let sanitizationFilter = function (content) {
for (const substitute in videoNameSanitization) {
for (const subject of videoNameSanitization[substitute]) {
content = content.replace(subject, substitute);
}
}
return content;
};
let sanitizeVideoPage = function () {
let videoTitle = document.querySelector('.inlineFree');
if (videoTitle !== null) {
let sanitizedVideoName = sanitizationFilter(videoTitle.textContent);
videoTitle.textContent = sanitizedVideoName;
document.title = sanitizedVideoName;
}
};
let sanitizeVideoItem = function (videoItem, videoName) {
videoItem.querySelector('.title > a').textContent = sanitizationFilter(videoName);
};
// Compliance logic
let complianceCallback = function (target, minDuration, minRating) {
let videoItems = target.querySelectorAll('.videoBox');
let videoName, videoComplies;
for (let videoItem of videoItems) {
videoName = videoItem.querySelector('.title > a').textContent;
logVideoName(videoName);
videoComplies =
validateBlacklist(videoItem, videoName) &&
validateDuration(videoItem, minDuration) &&
validateHD(videoItem) &&
validatePrivateVideo(videoItem) &&
validateRating(videoItem, minRating);
if (videoComplies) {
videoItem.style.display = 'inline-block';
sanitizeVideoItem(videoItem, videoName);
} else {
videoItem.style.display = 'none';
}
logSeparator();
}
};
let observer = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
complianceCallback(mutation.target, defaultMinimumDurationInSeconds, defaultMinimumRating);
}
});
// Building UI
if (showUI) {
// Settings UI
let addCategoryDiv = function () {
let categoryDiv = document.createElement('div');
categoryDiv.style.display = 'block';
categoryDiv.style.height = '18px';
categoryDiv.style.marginBottom = '2px';
categoryDiv.style.padding = '5px';
return categoryDiv;
};
let addCategoryLabel = function (category, inputIDSuffix) {
let categoryLabel = document.createElement('label');
categoryLabel.style.float = 'left';
categoryLabel.style.padding = '2px 5px 5px 0';
categoryLabel.setAttribute('for', 'ph-sui-' + inputIDSuffix);
categoryLabel.textContent = category + ': ';
return categoryLabel;
};
let addCategoryInput = function (inputIDSuffix, defaultValue) {
let categoryInput = document.createElement('input');
categoryInput.id = 'ph-sui-'+ inputIDSuffix;
categoryInput.style.float = 'right';
categoryInput.style.width = '100px';
categoryInput.style.textAlign = 'center';
categoryInput.value = defaultValue;
return categoryInput;
};
let addCategory = function (category, inputIDSuffix, defaultInputValue) {
let categoryDiv = addCategoryDiv();
let categoryLabel = addCategoryLabel(category, inputIDSuffix);
let categoryInput = addCategoryInput(inputIDSuffix, defaultInputValue);
categoryDiv.appendChild(categoryLabel);
categoryDiv.appendChild(categoryInput);
return categoryDiv;
};
// --Configurable sections
let durationSection = addCategory('Min Duration', 'min-duration', defaultMinimumDurationInSeconds.toString());
let ratingSection = addCategory('Min Rating', 'min-rating', defaultMinimumRating.toString());
// --Submit
let applyButton = document.createElement('button');
applyButton.textContent = 'Apply';
applyButton.style.height = '30px';
applyButton.style.width = '100%';
// --Section
let section = document.createElement('section');
section.id = 'ph-search-and-tweaks-settings';
section.style.display = 'none';
section.style.position = 'fixed';
section.style.top = '250px';
section.style.left = '0';
section.style.width = '200px';
section.style.padding = '1px';
section.style.backgroundColor = '#ffa31a';
section.style.zIndex = '1000';
section.appendChild(durationSection);
section.appendChild(ratingSection);
section.appendChild(applyButton);
let bodyTag = document.getElementsByTagName('body')[0];
bodyTag.appendChild(section);
// --Events
applyButton.addEventListener('click', function () {
let videoLists = document.querySelectorAll('.videos');
let minDuration = document.getElementById('ph-sui-min-duration').value;
let minRating = document.getElementById('ph-sui-min-rating').value;
for (let videoList of videoLists) {
complianceCallback(videoList, minDuration, minRating);
}
});
logAction('Building UI');
// Settings UI Control
let networkBarList = document.querySelector('#networkbar_items_wrap');
let controlListItem = document.createElement('li');
let controlButton = document.createElement('button');
controlButton.textContent = 'Search & Tweaks';
controlButton.style.width = '100%';
controlButton.style.margin = '2px 5px';
controlButton.style.padding = '2px 5px';
controlButton.style.backgroundColor = '#ffa31a';
controlButton.style.border = 0;
controlListItem.appendChild(controlButton);
networkBarList.appendChild(controlListItem);
// --Events
controlButton.addEventListener('click', function () {
let settingsUI = document.querySelector('#ph-search-and-tweaks-settings');
if (settingsUI.style.display === 'none') {
settingsUI.style.display = 'block';
} else {
settingsUI.style.display = 'none';
}
});
}
// -- Move pagination section
let videosSection = document.querySelector('.nf-videos');
if (videosSection !== null) {
let paginationBlock = document.querySelector('.pagination3');
videosSection.appendChild(paginationBlock);
}
// -- Initial compliance run & observer attachment
let videoLists = document.querySelectorAll('ul.videos');
for (let videoList of videoLists) {
complianceCallback(videoList, defaultMinimumDurationInSeconds, defaultMinimumRating);
observer.observe(videoList, observerConfig);
}
sanitizeVideoPage();
logAction('Initial run and observer attachment.');
// -- Ad blocking
//let ads = target.querySelectorAll('.removeAdLink');
//if (debug) { console.log('Ads Found: ' + ads.length); }
//for (let ad of ads) {
//ad.parentNode.remove();
//}
// -- IFrames removal
if (removeIFrames) {
let IFrames = document.querySelectorAll('iframe');
for (let IFrame of IFrames) {
IFrame.remove();
}
logAction('Remove all IFrames.');
}
// -- Live models removal
if (removeLiveModelsSection) {
let liveModelStreamsSection = document.querySelector('.streamateContent');
if (liveModelStreamsSection !== null) {
liveModelStreamsSection.closest('.sectionWrapper').remove();
}
logAction('Live model section removal.');
}
// Porn stars listing in sidebar removal
if (removePornStarsListingInSidebar) {
let pornStarsSection = document.getElementById('relatedPornstarSidebar');
if (pornStarsSection !== null) {
pornStarsSection.remove();
}
logAction('Sidebar porn start listing removal.');
}