PH - Search & UI Tweaks

Various search filters and user experience enhancers

As of 2018-11-05. See the latest version.

// ==UserScript==
// @name          PH - Search & UI Tweaks
// @namespace     brazenvoid
// @version       1.1.1
// @author        brazenvoid
// @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'],
    'BF': ['stepbrother', 'brother', 'daddy', 'dad'],
    'GF': ['daughter', 'mom', 'mother', 'step-sister', 'stepsister', '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; // Only recommended for desktops!

const enableDebugLogging = false;

// Setting up configuration

let observerConfig = {
    attributes: false,
    childList: true,
    subtree: false
};

// Prebuilding 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, defaultMinimumRating);
    }
});

// Building UI

if (showUI) {

    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.style.display = 'block';
    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');
}

// -- 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.');
}