// ==UserScript==
// @name PH - Search & UI Tweaks
// @namespace brazenvoid
// @version 1.7.1
// @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 settings = {
blacklist: [ // case-insensitive
'urin',
'arab',
'muslim',
'desi',
'squirt',
'fake',
'pregnant',
'pee',
],
sanitize: { // Substitutes values with key (case-insensitive)
' ': ['neighbor', 'step'],
'Boyfriend': ['brother', 'bro', 'daddy', 'dad'],
'Girlfriend': ['daughter', 'mom', 'mother', 'sister', 'sis']
},
duration: [60, 0], // In Seconds
rating: [80, 0],
views: [0, 0],
fixCounterHeight: true,
hideSDVideos: true,
hidePrivateVideos: true,
hideUnratedVideos: true,
hideWatchedVideos: false,
removeIFrames: true,
removeLiveModelsSection: true,
removePornStarsListingInSidebar: true,
showUIAlways: false, // Desktop/Tablet
debugLogging: false
};
// Base Methods
// -- Selector naming
const selectorPrefix = 'ph-sui-';
let getSelector = function (selector) {
return selectorPrefix + selector;
};
let getStatLabelSelector = function (type) {
return getSelector(type + '-stat')
};
// -- Logging
let log = function (message) {
if (settings.debugLogging) {
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);
};
// -- Statistics
let statistics;
let recordStatistic = function (validationStatus, type) {
if (!validationStatus) {
if (typeof statistics[type] !== 'undefined') {
statistics[type] += 1;
} else {
statistics[type] = 1;
}
statistics.total += 1;
}
};
let resetStatistic = function () {
statistics = {total: 0};
};
let updateStatisticUI = function () {
let statLabel;
for (const statisticType in statistics) {
statLabel = document.getElementById(getStatLabelSelector(statisticType));
statLabel.textContent = statistics[statisticType];
}
};
// -- UI
let addFormButton = function (caption, onClick) {
let button = document.createElement('button');
button.textContent = caption;
button.style.height = '30px';
button.style.width = '100%';
button.addEventListener('click', onClick);
return button;
};
let addFormGroup = function () {
let divFormGroup = document.createElement('div');
divFormGroup.style.display = 'block';
divFormGroup.style.height = '18px';
divFormGroup.style.marginBottom = '2px';
divFormGroup.style.padding = '5px 0';
return divFormGroup;
};
let addFormGroupLabel = function (label, id) {
let labelFormGroup = document.createElement('label');
labelFormGroup.style.float = 'left';
labelFormGroup.style.padding = '2px 0';
labelFormGroup.setAttribute('for', getSelector(id));
labelFormGroup.textContent = label + ': ';
return labelFormGroup;
};
let addFormGroupStatLabel = function (statisticType) {
let labelFormGroup = document.createElement('label');
labelFormGroup.id = getStatLabelSelector(statisticType);
labelFormGroup.style.float = 'right';
labelFormGroup.style.padding = '2px 0';
labelFormGroup.textContent = '0';
return labelFormGroup;
};
let addFormGroupInput = function (id, defaultValue) {
let inputFormGroup = document.createElement('input');
inputFormGroup.id = getSelector(id);
inputFormGroup.style.float = 'right';
inputFormGroup.style.width = '100px';
inputFormGroup.style.textAlign = 'center';
inputFormGroup.value = defaultValue;
return inputFormGroup;
};
let addSeparator = function () {
let separator = document.createElement('hr');
separator.style.margin = '5px 5px 0 5px';
return separator;
};
let addSettingsFormGroup = function (label, id, defaultValue) {
let divFormGroup = addFormGroup();
let labelFormGroup = addFormGroupLabel(label, id);
let inputFormGroup = addFormGroupInput(id, defaultValue);
divFormGroup.appendChild(labelFormGroup);
divFormGroup.appendChild(inputFormGroup);
return divFormGroup;
};
let addStatisticsFormGroup = function (label, id) {
let divFormGroup = addFormGroup();
let labelFormGroup = addFormGroupLabel('Filtered ' + label + ' Videos', id);
let statLabelFormGroup = addFormGroupStatLabel(id);
divFormGroup.appendChild(labelFormGroup);
divFormGroup.appendChild(statLabelFormGroup);
return divFormGroup;
};
// -- Filters
// -- -- Range Filtering
let rangeFilter = function (value, bounds) {
let validationCheck = true;
if (bounds[0] > 0) {
validationCheck = value >= bounds[0];
}
if (bounds[1] > 0) {
validationCheck = value <= bounds[1];
}
return validationCheck;
};
// -- -- Blacklist validation
let validateBlacklist = function (videoItem, videoName) {
let validationCheck = true;
if (settings.blacklist.length > 0) {
for (const blacklistedWord of settings.blacklist) {
validationCheck = videoName.match(blacklistedWord) === null;
if (!validationCheck) {
break;
}
}
logValidationResult('Blacklist', validationCheck);
recordStatistic(validationCheck, 'blacklisted');
}
return validationCheck;
};
// -- -- Duration validation
let validateDuration = function (videoItem) {
let validationCheck = true;
if (settings.duration[0] > 0 || settings.duration[1] > 0) {
let duration = videoItem.querySelector('.duration').textContent.split(':');
duration = (parseInt(duration[0]) * 60) + parseInt(duration[1]);
validationCheck = rangeFilter(duration, settings.duration);
logValidationResult('Duration', validationCheck);
recordStatistic(validationCheck, 'short');
}
return validationCheck;
};
// -- -- High definition validation
let validateHD = function (videoItem) {
let validationCheck = true;
if (settings.hideSDVideos) {
validationCheck = videoItem.querySelector('.hd-thumbnail') !== null;
logValidationResult('HD', validationCheck);
}
return validationCheck;
};
// -- -- Private video validation
let validatePrivateVideo = function (videoItem) {
let validationCheck = true;
if (settings.hidePrivateVideos) {
validationCheck = videoItem.querySelector('.privateOverlay .fanOnlyVideoWidget ') === null;
logValidationResult('Private Video', validationCheck);
}
return validationCheck;
};
// -- -- Rating validation
let validateRating = function (videoItem) {
let validationCheck = true;
if (settings.rating[0] > 0 || settings.rating[1] > 0) {
let rating = videoItem.querySelector('.value');
if (rating === null) {
validationCheck = !settings.hideUnratedVideos;
} else {
rating = parseInt(rating.textContent.replace('%', ''));
validationCheck = rangeFilter(rating, settings.rating);
}
logValidationResult('Rating', validationCheck);
recordStatistic(validationCheck, 'rating');
}
return validationCheck;
};
// -- -- Video name sanitation
let sanitationFilter = function (content) {
for (const substitute in settings.sanitize) {
for (const subject of settings.sanitize[substitute]) {
content = content.replace(subject, substitute);
}
}
return content;
};
let sanitizeVideoPage = function () {
let videoTitle = document.querySelector('.inlineFree');
if (videoTitle !== null) {
let sanitizedVideoName = sanitationFilter(videoTitle.textContent);
videoTitle.textContent = sanitizedVideoName;
document.title = sanitizedVideoName;
}
};
let sanitizeVideoItem = function (videoItem, videoName) {
videoItem.querySelector('.title > a').textContent = sanitationFilter(videoName);
};
// -- -- View count validation
let validateViews = function (videoItem) {
let validationCheck = true;
if (settings.views[0] > 0 || settings.views[1] > 0) {
let viewsCountString = videoItem.querySelector('.views').textContent.replace(' views', '');
let viewsCountMultiplier = 1;
let viewsCountStringLength = viewsCountString.length;
if (viewsCountString[viewsCountStringLength - 1] === 'K') {
viewsCountMultiplier = 1000;
viewsCountString = viewsCountString.replace('K', '');
} else if (viewsCountString[viewsCountStringLength - 1] === 'M') {
viewsCountMultiplier = 1000000;
viewsCountString = viewsCountString.replace('M', '');
}
let viewsCount = parseFloat(viewsCountString) * viewsCountMultiplier;
validationCheck = rangeFilter(viewsCount, settings.views);
logValidationResult('View Count', validationCheck);
recordStatistic(validationCheck, 'views');
}
return validationCheck;
};
// -- -- Watched video validation
let validateWatchStatus = function (videoItem) {
let validationCheck = true;
if (settings.hideWatchedVideos) {
validationCheck = videoItem.querySelector('.watchedVideoOverlay') === null;
logValidationResult('Watched', validationCheck);
recordStatistic(validationCheck, 'watched');
}
return validationCheck;
};
// -- Compliance logic
let complianceCallback = function (target) {
let videoItems = target.querySelectorAll('.videoblock');
let videoName, videoComplies;
resetStatistic();
for (let videoItem of videoItems) {
videoName = videoItem.querySelector('.title > a').textContent;
logVideoName(videoName);
videoComplies =
validatePrivateVideo(videoItem) &&
validateWatchStatus(videoItem) &&
validateHD(videoItem) &&
validateRating(videoItem) &&
validateBlacklist(videoItem, videoName) &&
validateDuration(videoItem) &&
validateViews(videoItem);
if (videoComplies) {
videoItem.style.display = 'inline-block';
sanitizeVideoItem(videoItem, videoName);
} else {
videoItem.style.display = 'none';
}
logSeparator();
}
updateStatisticUI();
};
// UI Composition
// -- Settings Dialog
// -- -- Section
let section = document.createElement('section');
section.id = getSelector('settings');
section.style.color = 'black';
section.style.display = settings.showUIAlways ? 'block' : 'none';
section.style.fontWeight = 'bold';
section.style.position = 'fixed';
section.style.top = '250px';
section.style.left = '0';
section.style.width = '200px';
section.style.padding = '5px 10px';
section.style.backgroundColor = '#ffa31a';
section.style.zIndex = '1000';
// -- -- Settings
let durationSection = addSettingsFormGroup('Min Duration', 'min-duration', settings.duration[0].toString());
let ratingSection = addSettingsFormGroup('Min Rating', 'min-rating', settings.rating[0].toString());
let viewsSection = addSettingsFormGroup('Min Views', 'min-views', settings.views[0].toString());
let applyButton = addFormButton('Apply', function () {
let videoLists = document.querySelectorAll('.videos');
settings.duration[0] = document.getElementById(getSelector('min-duration')).value;
settings.rating[0] = document.getElementById(getSelector('min-rating')).value;
settings.views[0] = document.getElementById(getSelector('min-views')).value;
for (let videoList of videoLists) {
complianceCallback(videoList);
}
});
// -- -- Statistics
let BlacklistedStatSection = addStatisticsFormGroup('Blacklisted', 'blacklisted');
let lowRatedStatSection = addStatisticsFormGroup('Low Rated', 'rating');
let lowViewsStatSection = addStatisticsFormGroup('By Views', 'views');
let shortStatSection = addStatisticsFormGroup('Short', 'short');
let watchedVideosStatSection = addStatisticsFormGroup('Watched', 'watched');
let totalVideosStatSection = addStatisticsFormGroup('', 'total');
// -- -- Hide Button
let hideButton = addFormButton('Hide', function () {
document.getElementById(getSelector('settings')).style.display = 'none';
});
// -- -- Composition
section.appendChild(durationSection);
section.appendChild(ratingSection);
section.appendChild(viewsSection);
section.appendChild(applyButton);
section.appendChild(addSeparator());
section.appendChild(shortStatSection);
section.appendChild(lowRatedStatSection);
section.appendChild(lowViewsStatSection);
section.appendChild(BlacklistedStatSection);
section.appendChild(watchedVideosStatSection);
section.appendChild(totalVideosStatSection);
section.appendChild(hideButton);
let bodyTag = document.getElementsByTagName('body')[0];
bodyTag.appendChild(section);
// -- Settings Button
// -- -- Section
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';
// -- -- Events
controlButton.addEventListener('click', function () {
let settingsUI = document.getElementById(getSelector('settings'));
settingsUI.style.display = settingsUI.style.display === 'none' ? 'block' : 'none';
});
// -- -- Composition
let controlListItem = document.createElement('li');
controlListItem.appendChild(controlButton);
let networkBarList = document.querySelector('.networkListContent');
networkBarList.appendChild(controlListItem);
logAction('Building UI');
// Optimizations and prep
// -- Pre-building regular expressions
for (let i = 0; i < settings.blacklist.length; i++) {
settings.blacklist[i] = new RegExp(settings.blacklist[i], 'ig');
}
for (const substitute in settings.sanitize) {
for (let i = 0; i < settings.sanitize[substitute].length; i++) {
settings.sanitize[substitute][i] = new RegExp(settings.sanitize[substitute][i], 'ig');
}
}
// -- Move pagination section
let videosSection = document.querySelector('.nf-videos');
if (videosSection !== null) {
let paginationBlock = document.querySelector('.pagination3');
videosSection.appendChild(paginationBlock);
}
// Script run
// -- Initial compliance run & observer attachment
let observer = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
complianceCallback(mutation.target);
}
});
let observerConfig = {
attributes: false,
childList: true,
subtree: false
};
let videoLists = document.querySelectorAll('ul.videos');
for (let videoList of videoLists) {
complianceCallback(videoList);
observer.observe(videoList, observerConfig);
}
sanitizeVideoPage();
logAction('Initial run and observer attachment.');
// -- Fix Counter Height
if (settings.fixCounterHeight) {
let counter = document.querySelector('.showingCounter');
if (counter !== null) {
counter.style.height = 'auto';
}
logAction('Fix counter height.');
}
// -- IFrames Removal
if (settings.removeIFrames) {
let iFramesFilter = function (target) {
let iFrames = target.getElementsByTagName('iFrame');
for (let iFrame of iFrames) {
iFrame.remove();
}
logAction('IFrames removal.');
};
let iFramesObserver = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
iFramesFilter(mutation.target);
}
});
let iFrameParents = document.querySelectorAll('.sectionWrapper, #videoSearchResult');
for (let iFrameParent of iFrameParents) {
iFramesFilter(iFrameParent);
iFramesObserver.observe(iFrameParent, observerConfig);
}
}
// -- Live Models Section Removal
if (settings.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 (settings.removePornStarsListingInSidebar) {
let pornStarsSection = document.getElementById('relatedPornstarSidebar');
if (pornStarsSection !== null) {
pornStarsSection.remove();
}
logAction('Sidebar porn start listing removal.');
}