SuperPanda / PornHub PornStar/Models Videos Link Grabber

// ==UserScript==
// @name PornHub PornStar/Models Videos Link Grabber
// @description This adds an "Get all video links" button on pornstar/model pages (in the tab -> videos) which opens an popup and show all video links from this pornstar/model as list
// @version 0.0.2
// @license MIT
// @author SuperPanda
// @match https://pornhub.com/pornstar/*
// @match https://*.pornhub.com/pornstar/*
// @match https://pornhub.com/pornstar/*/videos
// @match https://*.pornhub.com/pornstar/*/videos
// @match https://pornhub.com/model/*/videos
// @match https://*.pornhub.com/model/*/videos
// @match https://pornhub.com/model/*
// @match https://*.pornhub.com/model/*
// @match https://pornhubpremium.com/pornstar/*/videos
// @match https://*.pornhubpremium.com/pornstar/*/videos
// @match https://pornhubpremium.com/model/*/videos
// @match https://*.pornhubpremium.com/model/*/videos
// @updateURL https://openuserjs.org/meta/SuperPanda/PornHub_PornStarModels_Videos_Link_Grabber.meta.js
// @downloadURL https://openuserjs.org/install/SuperPanda/PornHub_PornStarModels_Videos_Link_Grabber.user.js
// @copyright 2020, SuperPanda (https://openuserjs.org/users/SuperPanda)
// ==/UserScript==
(function () {
  'use strict'

  addcss(`.loader-links {
        position: fixed;
        top: 40vh;
    }

    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }

    .loader-links-wrapper {
        align-items: center;
        position: fixed;
        background-color: rgba(0,0,0,0.8);
        display: flex;
        height: 100%;
        width: 100%;
        justify-content: center;
        margin: 0;
        z-index: 500;
        overflow: hidden;
    }

    .loader-links {
        width: 60px;
    }

    .loader-links-wheel {
        animation: spin 1s infinite linear;
        border: 2px solid rgba(30, 30, 30, 0.5);
        border-left: 4px solid #fff;
        border-radius: 50%;
        height: 50px;
        margin-bottom: 10px;
        width: 50px;
    }

    .loader-links-text {
        color: #fff;
        font-family: arial, sans-serif;
    }

    .loader-links-text:after {
        content: 'Loading';
        animation: load 2s linear infinite;
    }

    @keyframes spin {
        0% {
            transform: rotate(0deg);
        }
        100% {
            transform: rotate(360deg);
        }
    }

    @keyframes load {
        0% {
            content: 'Loading';
        }
        33% {
            content: 'Loading.';
        }
        67% {
            content: 'Loading..';
        }
        100% {
            content: 'Loading...';
        }
    }`)

  var activeLog = false
  var linkList = []
  var currentUrl = window.location.href

  if (document.querySelector('.sectionTitle .spriteProfileIcons') !== null){
      var btnContainer = document.querySelector('.sectionTitle .spriteProfileIcons')
  } else {
      var btnContainer = document.querySelector('.section_header .spriteProfileIcons .mediumPlayAllBtn')
  }

  var linkGrabberBtn = document.createElement('a')
  linkGrabberBtn.text = 'Get all video links'
  linkGrabberBtn.onclick = startGettingAllTheLinks
  linkGrabberBtn.classList.add('greyButton')
  linkGrabberBtn.classList.add('float-right')
  //btnContainer.appendChild(linkGrabberBtn)

  btnContainer.insertBefore(linkGrabberBtn, btnContainer.childNodes[0])


  function validURL(str) {
      var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
                               '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
                               '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
                               '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
                               '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
                               '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
      return !!pattern.test(str);
  }

  function startGettingAllTheLinks () {
    Promise.resolve(ShowLoader()) // FIXME This doesn't work for some reason
      .then(getLinks)
      .then(() => {
        if (activeLog == true) { console.log('found ' + linkList.length + ' links') }
        for (var i = 0; i < linkList.length; i++) {
          if (activeLog == true) { console.log(linkList[i].href) }
        }
      })
      .then(showLinks)
  }

  const wait = ms => new Promise((resolve) => setTimeout(resolve, ms))

  const getLinks = async function (index = 1) {
    let url = currentUrl + '?page=' + index
    let responseData
    try {
      responseData = await makeRequest('GET', url, index)
    } catch (error) {
      if (activeLog == true) { console.log(error) }
    }
    if (responseData) {
      if (responseData.status && responseData.status >= 200 && responseData.status < 300) {
        extractLinks(responseData.response)
        if (activeLog == true) { console.log('parsed page ' + index) }
        await getLinks(index + 1)
      } else {
        switch (responseData.status) {
          case 404:
            if (activeLog == true) { console.log('404 for page ' + index + '. Looks like ' + (index - 1) + ' is the last page.') }
            break
          case 429:
            if (activeLog == true) { console.log('Too many requests. I\'ll wait a bit and try again') }
            await wait(1000)
            await getLinks(index)
            break
          default:
            console.error('Augh, there was an error!', responseData.status, responseData.statusText, index)
            break
        }
      }
    } else {
      if (activeLog == true) { console.log('now this is unexpected ...') }
    }
  }

  function extractLinks (response) {
    var links = response.querySelectorAll('div.sectionWrapper ul.videos li .linkVideoThumb')
    for (var i = 0; i < links.length; i++) {
      linkList.push(links[i])
    }
  }

  function makeRequest (method, url, pageIndex) {
    if (activeLog == true) { console.log('new Request ' + method + ' ' + url) }
    return new Promise(function (resolve, reject) {
      var xhr = new XMLHttpRequest()
      xhr.open(method, url)
      xhr.responseType = 'document'
      xhr.onload = function () {
        resolve({
          response: xhr.response,
          status: this.status,
          statusText: xhr.statusText,
          pageIndex: pageIndex
        })
      }
      xhr.onerror = function () {
        reject(new Error('the request ' + method + ' ' + url + ' failed'))
      }
      xhr.send()
    })
  }

  function ShowLoader () {

    var loader_wrapper = document.createElement('div')
    loader_wrapper.className = 'loader-links-wrapper'
    loader_wrapper.id = 'createLinksLoader'

    var loader = document.createElement('div')
    var loader_wheel = document.createElement('div')
    var loader_text = document.createElement('div')
    loader.className = 'loader-links'
    loader_wheel.className = 'loader-links-wheel'
    loader_text.className = 'loader-links-text'

    loader_wrapper.appendChild(loader)
    loader.appendChild(loader_wheel)
    loader.appendChild(loader_text)

    var addLoader = document.querySelector('.wrapper')
    addLoader.insertBefore(loader_wrapper, addLoader.childNodes[0])
  }

  function RemoveLoader () {
    var loader = document.getElementById('createLinksLoader')
    if (loader) {
            loader.style.transition = '.5s';
            loader.style.opacity = '0';
            loader.style.visibility = 'hidden';
    }
  }

  function addcss (css) {
    var head = document.getElementsByTagName('head')[0]
    var s = document.createElement('style')
    s.setAttribute('type', 'text/css')
    if (s.styleSheet) { // IE
      s.styleSheet.cssText = css
    } else { // the world
      s.appendChild(document.createTextNode(css))
    }
    head.appendChild(s)
  }

  function showLinks () {
    var outerModalDiv = document.createElement('div')
    var innerModalDiv = document.createElement('div')
    outerModalDiv.id = 'pornstarVidsLinkContainingModalPanel'
    outerModalDiv.style.display = 'block'
    outerModalDiv.style.position = 'fixed'
    outerModalDiv.style.zIndex = '100'
    outerModalDiv.style.paddingTop = '100px'
    outerModalDiv.style.left = '0'
    outerModalDiv.style.top = '0'
    outerModalDiv.style.width = '100%'
    outerModalDiv.style.height = '100%'
    outerModalDiv.style.overflow = 'auto'
    outerModalDiv.style.backgroundColor = 'rgb(0,0,0)'
    outerModalDiv.style.backgroundColor = 'rgb(0,0,0,0.4)'

    var closeButtonContainer = document.createElement('div')
    closeButtonContainer.className = 'userButtons'
    var closeButton = CreateButton('X', null, RemoveOuterModalPanel)
    closeButton.style.cssFloat = 'right'
    closeButtonContainer.appendChild(closeButton)
    innerModalDiv.appendChild(closeButtonContainer)

    innerModalDiv.style.backgroundColor = '#1b1b1b'
    innerModalDiv.style.margin = 'auto'
    innerModalDiv.style.padding = '20px'
    innerModalDiv.style.border = '1px solid #888'
    innerModalDiv.style.width = '50%'
    innerModalDiv.style.color = '#ababab'
    innerModalDiv.style.textAlign = 'center'

    var founded = document.createElement('p')
    founded.innerHTML = 'I have found '+ linkList.length + ' video links.'
    innerModalDiv.appendChild(founded)

    var linkListDiv

    linkListDiv = document.createElement('textarea')
    linkListDiv.readOnly = true;
    linkListDiv.style.width = '600px'
    linkListDiv.style.height = '300px'

    for (var i = 0; i < linkList.length; i++) {
        if (validURL(linkList[i].href)) {
            var span = document.createElement('p')
            span.innerHTML = linkList[i].href + '&#10;'
            linkListDiv.innerHTML += linkList[i].href + '&#10;';
        }
    }
    RemoveLoader()
    innerModalDiv.appendChild(linkListDiv)
    outerModalDiv.appendChild(innerModalDiv)
    document.body.appendChild(outerModalDiv)
  }

  function CreateButton (text, id, onClickEvent) {
    var innerbutton = document.createElement('button')
    innerbutton.innerText = text
    innerbutton.className = 'buttonBase'
    innerbutton.style.backgroundColor = '#f90'
    innerbutton.style.color = '#000'
    innerbutton.style.fontWeight = '700'
    innerbutton.display = 'inline-block'

    var button = document.createElement('div')
    if (id) button.id = id

    button.style.padding = '5px 10px'
    button.style.lineHeight = '1.2em'
    button.style.borderRadius = '4px'
    button.onclick = onClickEvent
    button.appendChild(innerbutton)
    return button
  }

  function RemoveOuterModalPanel () {
    var toRemove = document.getElementById('pornstarVidsLinkContainingModalPanel')
    toRemove.parentNode.removeChild(toRemove)
  }
})()