SuperPanda / PornHub Channel Videos Link Grabber

// ==UserScript==
// @name PornHub Channel Videos Link Grabber
// @description This adds an "Get all video links" button on channel pages (in the tab -> videos) which opens an popup and show all video links from this channel as list
// @version 0.0.2
// @license MIT
// @match https://pornhub.com/channels/*
// @match https://*.pornhub.com/channels/*
// @match https://pornhubpremium.com/channels/*
// @match https://*.pornhubpremium.com/channels/*
// @updateURL https://openuserjs.org/meta/SuperPanda/PornHub_Channel_Videos_Link_Grabber.meta.js
// @downloadURL https://openuserjs.org/install/SuperPanda/PornHub_Channel_Videos_Link_Grabber.user.js
// @copyright 2020, SuperPanda (https://openuserjs.org/users/SuperPanda)
// @grant GM.xmlHttpRequest
// ==/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: 100vh;
        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 = true
  var linkList = []
  var currentUrl = window.location.href
  var paginationBaseUrl = currentUrl
  var btnContainer = document.querySelector('.rightSide .sectionChannelsWrapper div.title')
  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)

  function startGettingAllTheLinks () {
    Promise.resolve(ShowLoader())
      .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 addQueryParam = function (url, paramName, paramValue) {
    if (url.indexOf('?') !== -1) {
      let newUrl = new URL(url)
      if (newUrl.searchParams.has(paramName)) {
        newUrl.searchParams.delete(paramName)
      }
      newUrl.searchParams.append(paramName, paramValue)
      return newUrl.toString()
    } else {
      return url + '?' + paramName + '=' + paramValue
    }
  }

  const getLinks = async function (index = 1) {
    let url
    let responseData
    if (currentUrl.indexOf('/videos') === -1) {
       url = currentUrl + '/videos?page=' + index
    } else {
       url = currentUrl + '?page=' + index
    }
    try {
      responseData = await makeRequest('GET', url)
    } 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.sectionChannelsWrapper ul.videos li .title a')
    for (var i = 0; i < links.length; i++) {
      linkList.push(links[i])
    }
  }

  function makeRequest (method, url) {
    if (activeLog == true) { console.log('new Request ' + method + ' ' + url) }
    return new Promise(function (resolve, reject) {
      GM.xmlHttpRequest({
        method: method,
        url: url,
        onload: function (response) {
          var responseXML = null
          responseXML = new DOMParser()
            .parseFromString(response.responseText, 'text/html')

          resolve({
            response: responseXML,
            status: response.status,
            statusText: response.statusText
          })
        },
        onerror: function (response) {
          reject(new Error('the request ' + method + ' ' + url + ' failed'))
        }
      })
    })
  }

  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.body
    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) {
      s.styleSheet.cssText = css
    } else {
      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++) {
        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)
  }
})()