PH - Search & UI Tweaks

Various search filters and user experience enhancers

Versión del día 07/12/2018. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name          PH - Search & UI Tweaks
// @namespace     brazenvoid
// @version       1.5.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 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 defaultMinimumDurationInSeconds = 120;
const defaultMinimumRating = 80;
const defaultMinimumViews = 0;

const hideNonHDVideosOnVideoPages = true;
const hidePrivateVideos = true;
const hideUnratedVideos = true;
const hideWatchedVideos = false;
const removeIFrames = true;
const removeLiveModelsSection = true;
const removePornStarsListingInSidebar = true;
const showUIAlways = false; // Desktop/Tablet

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

// -- 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 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
// -- -- Blacklist validation

let validateBlacklist = function (videoItem, videoName) {

    let validationCheck = true;

    if (blacklistedWords.length > 0) {

        for (const blacklistedWord of blacklistedWords) {

            validationCheck = videoName.match(blacklistedWord) === null;

            if (!validationCheck) {
                break;
            }
        }
        logValidationResult('Blacklist', validationCheck);
        recordStatistic(validationCheck, 'blacklisted');
    }
    return validationCheck;
};

// -- -- Duration validation

let validateDuration = function (videoItem, minDuration) {

    let validationCheck = true;

    if (minDuration > 0) {

        let duration = videoItem.querySelector('.duration').textContent.split(':');
        duration = (parseInt(duration[0]) * 60) + parseInt(duration[1]);

        validationCheck = duration >= minDuration;

        logValidationResult('Duration', validationCheck);
        recordStatistic(validationCheck, 'short');
    }
    return validationCheck;
};

// -- -- High definition validation

let validateHD = function (videoItem) {

    let validationCheck = true;

    if (hideNonHDVideosOnVideoPages) {

        validationCheck = videoItem.querySelector('.hd-thumbnail') !== null;

        logValidationResult('HD', validationCheck);
    }
    return validationCheck;
};

// -- -- Private video validation

let validatePrivateVideo = function (videoItem) {

    let validationCheck = true;

    if (hidePrivateVideos) {

        validationCheck = videoItem.querySelector('.privateOverlay') === null;

        logValidationResult('Private Video', validationCheck);
    }
    return validationCheck;
};

// -- -- Rating validation

let validateRating = function (videoItem, minRating) {

    let validationCheck = true;

    if (minRating > 0) {

        let rating = videoItem.querySelector('.value');

        if (rating === null) {
            validationCheck = !hideUnratedVideos;
        } else {
            rating = parseInt(rating.textContent.replace('%', ''));
            validationCheck = rating >= minRating;
        }
        logValidationResult('Rating', validationCheck);
        recordStatistic(validationCheck, 'rating');
    }
    return validationCheck;
};

// -- -- Video name sanitation

let sanitationFilter = 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 = 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, minViews) {

    let validationCheck = true;

    if (minViews > 0) {

        let viewsCount = videoItem.querySelector('.views').textContent.replace(' views', '');
        let viewsCountMultiplier = 1;
        let viewsCountStringLength = viewsCount.length;

        if (viewsCount[viewsCountStringLength - 1] === 'K') {

            viewsCountMultiplier = 1000;
            viewsCount = viewsCount.replace('K', '');

        } else if (viewsCount[viewsCountStringLength - 1] === 'M') {

            viewsCountMultiplier = 1000000;
            viewsCount = viewsCount.replace('M', '');
        }

        validationCheck = (parseFloat(viewsCount) * viewsCountMultiplier) > minViews;

        logValidationResult('View Count', validationCheck);
        recordStatistic(validationCheck, 'views');
    }
    return validationCheck;
};

// -- -- Watched video validation

let validateWatchStatus = function (videoItem) {

    let validationCheck = true;

    if (hideWatchedVideos) {

        validationCheck = videoItem.querySelector('.watchedVideoOverlay') === null;

        logValidationResult('Watched', validationCheck);
        recordStatistic(validationCheck, 'watched');
    }
    return validationCheck;
};

// -- Compliance logic

let complianceCallback = function (target, minDuration, minRating, minViews) {

    let videoItems = target.querySelectorAll('.videoblock');
    let videoName, videoComplies;

    resetStatistic();

    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) &&
            validateViews(videoItem, minViews) &&
            validateWatchStatus(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 = 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', defaultMinimumDurationInSeconds.toString());
let ratingSection = addSettingsFormGroup('Min Rating', 'min-rating', defaultMinimumRating.toString());
let viewsSection = addSettingsFormGroup('Min Views', 'min-views', defaultMinimumViews.toString());

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

// -- -- Submit

let applyButton = document.createElement('button');
applyButton.textContent = 'Apply';
applyButton.style.height = '30px';
applyButton.style.width = '100%';

// -- -- Events

applyButton.addEventListener('click', function () {

    let videoLists = document.querySelectorAll('.videos');
    let minDuration = document.getElementById(getSelector('min-duration')).value;
    let minRating = document.getElementById(getSelector('min-rating')).value;
    let minViews = document.getElementById(getSelector('min-views')).value;

    for (let videoList of videoLists) {
        complianceCallback(videoList, minDuration, minRating, minViews);
    }
});

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

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

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

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

let videoLists = document.querySelectorAll('ul.videos');
for (let videoList of videoLists) {
    complianceCallback(videoList, defaultMinimumDurationInSeconds, defaultMinimumRating, defaultMinimumViews);
    observer.observe(videoList, observerConfig);
}
sanitizeVideoPage();

logAction('Initial run and observer attachment.');

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