PH - Search & UI Tweaks

Various search filters and user experience enhancers

As of 2019-02-23. See the latest version.

// ==UserScript==
// @name          PH - Search & UI Tweaks
// @namespace     brazenvoid
// @version       1.10.3
// @author        brazenvoid
// @license       GPL-3.0-only
// @description   Various search filters and user experience enhancers
// @include       https://www.pornhub.com/*
// @require       https://greasyfork.org/scripts/375557-brazenvoid-s-base-resource/code/Brazenvoid's%20Base%20Resource.js?version=673194
// @grant         GM_addStyle
// @run-at        document-idle
// ==/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: { // In Seconds
    minimum: 120,
    maximum: 0
  },
  rating: {
    minimum: 80,
    maximum: 0
  },
  views: {
    minimum: 0,
    maximum: 0
  },
  hideSDVideos: true,
  hidePrivateVideos: true,
  hideUnratedVideos: true,
  hideWatchedVideos: false,
  removeIFrames: true,
  removeLiveModelsSection: true,
  removePornStarsListingInSidebar: true,
  showUIAlways: false, // Desktop
  debugLogging: false
}

// Base Resources Initialization

const scriptPrefix = 'az-sui-'

let storage = new LocalStore(scriptPrefix + 'settings', settings)
settings = storage.retrieve().get()

let logger = new Logger(settings.debugLogging)
let selectorGenerator = new SelectorGenerator(scriptPrefix)
let statistics = new StatisticsRecorder(logger, selectorGenerator)
let uiGenerator = new UIGenerator(settings.showUIAlways, selectorGenerator)

let filters = new Filters(statistics)
filters.blacklist = settings.blacklist
filters.sanitizationRules = settings.sanitize
filters.init()

// Local Store Events

let refreshUI = function () {
  let store = this.get()

  document.getElementById(selectorGenerator.getSettingsInputSelector('Min Duration')).value = store.duration.minimum
  document.getElementById(selectorGenerator.getSettingsInputSelector('Min Rating')).value = store.rating.minimum
  document.getElementById(selectorGenerator.getSettingsInputSelector('Min Views')).value = store.views.minimum
}
storage.onDefaultsLoaded = refreshUI
storage.onRetrieval = refreshUI
storage.onUpdated = refreshUI

// Validators
// -- Duration validation

let validateDuration = function (videoItem) {

  if (settings.duration.minimum > 0 || settings.duration.maximum > 0) {

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

    return filters.validateRange('Duration', duration, [settings.duration.minimum, settings.duration.maximum])
  }
  return true
}

// -- High definition validation

let validateHD = function (videoItem) {

  let validationCheck = true

  if (settings.hideSDVideos) {

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

    logger.logValidation('HD', validationCheck)
  }
  return validationCheck
}

// -- Private video validation

let validatePrivateVideo = function (videoItem) {

  let validationCheck = true

  if (settings.hidePrivateVideos) {

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

    logger.logValidation('Private Video', validationCheck)
  }
  return validationCheck
}

// -- Rating validation

let validateRating = function (videoItem) {

  let validationCheck = true

  if (settings.rating.minimum > 0 || settings.rating.maximum > 0) {

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

    if (rating === null) {
      validationCheck = !settings.hideUnratedVideos
      statistics.record('Rating', validationCheck)
    } else {
      rating = parseInt(rating.textContent.replace('%', ''))
      if (rating === 0) {
        validationCheck = !settings.hideUnratedVideos;
      } else {
        validationCheck = filters.validateRange('Rating', rating, [settings.rating.minimum, settings.rating.maximum])
      }
    }
  }
  return validationCheck
}

// -- Video name sanitation

let sanitizeVideoPage = function () {

  let videoTitle = document.querySelector('.inlineFree')
  if (videoTitle !== null) {

    let sanitizedVideoName = filters.sanitize(videoTitle.textContent)
    videoTitle.textContent = sanitizedVideoName
    document.title = sanitizedVideoName
  }

}
let sanitizeVideoItem = function (videoItem, videoName) {

  videoItem.querySelector('.title > a').textContent = filters.sanitize(videoName)
}

// -- View count validation

let validateViews = function (videoItem) {

  if (settings.views.minimum > 0 || settings.views.maximum > 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

    return filters.validateRange('Views', viewsCount, [settings.views.minimum, settings.views.maximum])
  }
  return true
}

// -- Watched video validation

let validateWatchStatus = function (videoItem) {

  let validationCheck = true

  if (settings.hideWatchedVideos) {

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

    statistics.record('Watched', validationCheck)
  }
  return validationCheck
}

// -- Compliance logic

let complianceCallback = function (target) {

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

  for (let videoItem of videoItems) {

    videoName = videoItem.querySelector('.title > a').textContent
    logger.logVideoCheck(videoName)

    videoComplies =
      validatePrivateVideo(videoItem) &&
      validateWatchStatus(videoItem) &&
      validateHD(videoItem) &&
      validateRating(videoItem) &&
      filters.validateBlackList(videoName) &&
      validateDuration(videoItem) &&
      validateViews(videoItem)

    if (videoComplies) {
      videoItem.style.display = 'inline-block'
      sanitizeVideoItem(videoItem, videoName)
    } else {
      videoItem.style.display = 'none'
    }

    logger.logSeparator()
  }
  statistics.updateUI()
}

// UI Composition
// -- Control Panel

let section = uiGenerator.createSection('settings', '#ffa31a', '150px', '200px', [
  uiGenerator.createSettingsFormGroup('Min Duration', 'number', settings.duration.minimum),
  uiGenerator.createSettingsFormGroup('Min Rating', 'number', settings.rating.minimum),
  uiGenerator.createSettingsFormGroup('Min Views', 'number', settings.views.minimum),
  uiGenerator.createSettingsFormGroup('Only HD Videos', 'checkbox', settings.hideSDVideos),
  uiGenerator.createSettingsFormGroup('Hide Watched Videos', 'checkbox', settings.hideWatchedVideos),
  uiGenerator.createSettingsFormGroup('Hide Unrated Videos', 'checkbox', settings.hideUnratedVideos),
  uiGenerator.createFormButton('Apply', function () {

    let videoLists = document.querySelectorAll('.videos')
    settings.duration.minimum = uiGenerator.getSettingsInputValue('Min Duration')
    settings.rating.minimum = uiGenerator.getSettingsInputValue('Min Rating')
    settings.views.minimum = uiGenerator.getSettingsInputValue('Min Views')
    settings.hideSDVideos = uiGenerator.getSettingsInputCheckedStatus('Only HD Videos')
    settings.hideWatchedVideos = uiGenerator.getSettingsInputCheckedStatus('Hide Watched Videos')
    settings.hideUnratedVideos = uiGenerator.getSettingsInputCheckedStatus('Hide Unrated Videos')

    statistics.reset()

    for (let videoList of videoLists) {
      complianceCallback(videoList)
    }
  }),
  uiGenerator.createSeparator(),
  uiGenerator.createStoreControlPanel(storage),
  uiGenerator.createSeparator(),
  uiGenerator.createStatisticsFormGroup('Duration', 'Short'),
  uiGenerator.createStatisticsFormGroup('Rating', 'Low Rated'),
  uiGenerator.createStatisticsFormGroup('Views', 'By Views'),
  uiGenerator.createStatisticsFormGroup('Blacklist', 'Blacklisted'),
  uiGenerator.createStatisticsFormGroup('Watched', 'Watched'),
  uiGenerator.createStatisticsFormGroup('Total'),
  uiGenerator.createFormButton('Hide', function () {
    document.getElementById(selectorGenerator.getSelector('settings')).style.display = 'none'
  }),
])
uiGenerator.appendToBody(section)
uiGenerator.appendToBody(uiGenerator.createSettingShowButton('Search & Tweaks', section))

logger.logTaskCompletion('Building UI')

// -- Move pagination section

let videosSection = document.querySelector('.nf-videos')
if (videosSection !== null) {
  let paginationBlock = document.querySelector('.pagination3')
  videosSection.appendChild(paginationBlock)
}

// -- Fix search section div heights

for (let div of document.querySelectorAll('.showingCounter, .tagsForWomen')) {
  div.style.height = 'auto'
}

// Script run
// -- Initial compliance run & observer attachment

let videoItemsObserver = new ChildObserver(complianceCallback)

let videoLists = document.querySelectorAll('ul.videos')
for (let videoList of videoLists) {
  videoItemsObserver.observe(videoList, true)
}
sanitizeVideoPage()

logger.logTaskCompletion('Initial run and observer attachment.')

// -- IFrames Removal

let removePHIframes = function () {

  let iframes = document.getElementsByTagName('milktruck')
  for (let iframe of iframes) {
    iframe.parentNode.remove()
  }
  return iframes.length
}

if (settings.removeIFrames) {
  filters.iFramesRemover()
  let iframesCount;
  do {
    iframesCount = removePHIframes()
  } while (iframesCount);
}

// -- Live Models Section Removal

if (settings.removeLiveModelsSection) {

  let liveModelStreamsSection = document.querySelector('.streamateContent')
  if (liveModelStreamsSection !== null) {
    liveModelStreamsSection.closest('.sectionWrapper').remove()
  }
  logger.logTaskCompletion('Live model section removal.')
}

// -- Porn stars listing in sidebar removal

if (settings.removePornStarsListingInSidebar) {

  let pornStarsSection = document.getElementById('relatedPornstarSidebar')
  if (pornStarsSection !== null) {
    pornStarsSection.remove()
  }
  logger.logTaskCompletion('Sidebar porn start listing removal.')
}