XVideos+

ad block, download ability without signing in, remember player settings and fast search

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name XVideos+
// @namespace -
// @version 2.3.3
// @description ad block, download ability without signing in, remember player settings and fast search
// @author NotYou
// @match *://xvideos.com/*
// @match *://xvideos2.com/*
// @match *://xvideos3.com/*
// @match *://xvideos4.com/*
// @match *://xvideos5.com/*
// @match *://xvideos53.com/*
// @match *://www.xvideos.com/*
// @match *://www.xvideos2.com/*
// @match *://www.xvideos3.com/*
// @match *://www.xvideos4.com/*
// @match *://www.xvideos5.com/*
// @match *://www.xvideos53.com/*
// @run-at document-end
// @license GPL-3.0-or-later
// @grant none
// ==/UserScript==

(function() {
    const TITLE = 'XVideos+'
    const LOCAL_VIDEO_SETTINGS = 'video_settings_xp'

    const DEFAULT_SETTINGS = JSON.stringify({
        volume: 1,
        muted: false,
        quality: 'Auto',
        speed: 1,
        loop: false
    })

    class CSS {
        static init() {
            const _CSS = `
            #site-logo-link {
              text-decoration: none;
            }

            #plus-xp {
              float: right;
              position: relative;
              margin-top: -10px;
              left: -6px;
              font-size: 36px;
              color: rgb(255, 255, 255);
              user-select: none;
              height: 0;
            }

            .no-text-dec, .no-text-dec:hover {
              text-decoration: none !important;
            }

            #fs-xp {
              background-color: rgba(213, 213, 213, .7);
              position: fixed;
              z-index: 2147483647;
              top: 50px;
              left: calc(60% / 2); /* 100% - 40% = 60% */
              width: 40%;
              border-radius: 5px;
            }

            #fs-input-parent-xp {
              display: flex;
              padding: .5rem;
            }

            #fs-input-parent-xp i {
              margin-top: 5px;
              margin-right: 5px;
              color: rgb(222, 38, 0);
            }

            #fs-input-parent-xp i::before {
              font-size: 20px;
            }

            #fs-input-xp {
              width: 100%;
              border-radius: 4px;
              height: 40px;
            }

            .tab-xp {
              background-color: rgb(247, 247, 247);
              margin: 0 0 10px 0;
              padding: 15px 10px;
            }

            #tabDownloadXp {
              text-align: center;
              font-size: 15px;
            }

            /* DARK-THEME */

            .dark-theme-xp #fs-xp {
              background-color: rgba(0, 0, 0, .7);
            }

            .dark-theme-xp .tab-xp {
              background-color: rgb(46, 46, 46);
            }`

            addStyle(_CSS)
        }
    }

    class DarkThemeChecker {
        static init() {
            let isDarkTheme

            setupDarkThemeClass()

            const DARK_THEME_OBS = new MutationObserver(setupDarkThemeClass)

            DARK_THEME_OBS.observe(document.body, {
                childList: true,
                subtree: true,
            })

            function setupDarkThemeClass() {
                setTheme()
                setDarkThemeClass()
            }

            function setTheme() {
                isDarkTheme = getTheme() === 'black'
            }

            function getTheme() {
                const USER_FAVORITE_THEME = localStorage.user_theme_fav
                const THEME = JSON.parse(USER_FAVORITE_THEME).i

                return THEME
            }

            function setDarkThemeClass() {
                document.documentElement.classList[isDarkTheme ? 'add' : 'remove']('dark-theme-xp')
            }
        }
    }

    class AntiAntiAdBlock {
        static init() {
            Object.defineProperty(window, 'fuckAdBlock', {
                get() {
                    return fakeFuckAdBlockObj()
                }
            })

            function fakeFuckAdBlockObj() {
                return {
                    _creatBait: blank('undef'),
                    _destroyBait: blank('undef'),
                    _checkBait: blank('undef'),
                    _log: blank('undef'),
                    _stopLoop: blank('undef'),

                    onDetected: blank(),
                    onNotDetected: blank(),
                    on: blank(),
                    setOptions: blank(),
                    setOption: blank(),
                    check: blank('false'),
                    emitEvent: blank(),
                    clearEvent: blank('undef'),
                }
            }

            function blank(type) {
                let returnValue

                switch (type) {
                    case 'undef': break
                    case 'false': returnValue = false; break
                    default: returnValue = this; break
                }

                return function() {
                    return returnValue
                }
            }
        }
    }

    class AdBlock {
        static init() {
            let css = ''
            const SELECTORS = [
                '#video-ad',
                '#video-ad + div[class]',
                '#ad-footer',
                '#ad-footer + .remove-ads',
                '#ad-footer2',
                '#video-sponsor-links',
                '.videoad-title',
                '.ad-footermobile',
                '.ad-support-tablet',
                '#ad-header-mobile-contener',
                '#v-actions .tabs .dl',
                '#content > :first-child > .clearfix',
                '.thumb-block-profile > .thumb-inside > .prof-thumb-title',
                '.thumb-ad'
            ]

            for (let i = 0; i < SELECTORS.length; i++) {
                const SELECTOR = SELECTORS[i]

                css += SELECTOR + '{ display: none !important; }'
            }

            addStyle(css)
        }
    }

    class PlusSymbol {
        static init() {
            let plus = document.createElement('span')
            plus.id = 'plus-xp'
            plus.className = 'noselect'
            plus.textContent = '+'

            let svgLogo = document.querySelector('#site-logo')

            if(svgLogo) {
                svgLogo.insertAdjacentElement('afterend', plus)
            }
        }
    }

    class FastSearch {
        static init() {
            let fastSearch = document.createElement('div')
            let fastSearchInputParent = document.createElement('div')
            let fastSearchIcon = document.createElement('i')
            let fastSearchInput = document.createElement('input')

            fastSearch.id = 'fs-xp'
            fastSearchInput.id = 'fp-input-xp'
            fastSearchInputParent.id = 'fs-input-parent-xp'

            fastSearchInput.placeholder = 'Fast Search XVideos'
            fastSearchInput.className = 'form-control'

            fastSearchIcon.className = 'icon-f icf-search'

            fastSearchIcon.addEventListener('click', fastSearchEventHandler)
            fastSearchInput.addEventListener('keydown', fastSearchEventHandler)

            fastSearchInputParent.appendChild(fastSearchIcon)
            fastSearchInputParent.appendChild(fastSearchInput)
            fastSearch.appendChild(fastSearchInputParent)

            toggleVisibility(fastSearch)

            document.body.appendChild(fastSearch)

            window.addEventListener('keydown', e => {
                if(e.code === 'KeyK' && (e.ctrlKey || e.metaKey)) {
                    e.preventDefault()
                    toggleVisibility(fastSearch)
                    fastSearchInput.focus()
                }
            })

            function fastSearchEventHandler(e) {
                if(e.type === 'click' || e.code === 'Enter') {
                    search(fastSearchInput.value)
                }
            }
        }
    }

    class CopyableTitle {
        static init() {
            const VIDEO_TITLE_NODE = document.querySelector('#title-auto-tr') || document.querySelector('.page-title')

            VIDEO_TITLE_NODE.addEventListener('click', () => {
                let videoTitleText = VIDEO_TITLE_NODE.firstChild.textContent.trim()

                if(!videoTitleText) {
                    videoTitleText = window.html5player.video_title
                }

                navigator.clipboard.writeText(videoTitleText)
            })
        }
    }

    class FastButtons {
        static init() {
            const VIDEO_TITLE = window.html5player.video_title

            let actionsTabs = document.querySelector('#v-actions .tabs')
            let videoTabs = document.querySelector('#video-tabs > .tabs')
            let dlBtnOrigin = actionsTabs.querySelector('.dl')

            let embedBtn = document.createElement('button')
            let embedLink = document.createElement('a')

            let dlBtn = document.createElement('button')
            let dlTab = document.createElement('div')
            let dlOptions = createDownloadOptions()

            embedLink.className = 'no-text-dec'
            embedLink.target = '_blank'
            embedLink.href = '/embedframe/' + window.html5player.id_video
            embedLink.innerHTML = '<span class="icon-f icf-embed"></span><span>Embed</span>'

            dlBtn.className = 'dl-xp'
            dlBtn.innerHTML = '<span class="icon-f icf-download"></span><span>Download (XVideos+)</span>'
            dlBtn.addEventListener('click', onDlClick)

            toggleVisibility(dlTab)
            dlTab.className = 'tab-xp'
            dlTab.id = 'tabDownloadXp'
            dlTab.innerHTML = 'Download ' + dlOptions
                .createOption('HIGH', window.html5player.url_high, 'High quality')
                .createOption('LOW', window.html5player.url_low, 'Low quality')
                .create()
            + ' quality video.'

            embedBtn.appendChild(embedLink)
            actionsTabs.appendChild(embedBtn)

            dlBtnOrigin.insertAdjacentElement('beforebegin', dlBtn)
            videoTabs.appendChild(dlTab)

            function onDlClick() {
                return toggleVisibility(dlTab)
            }

            function createDownloadOptions() {
                let options = ''

                return {
                    createOption(label, url, desc) {
                        options += `<strong><a href="${url}" title="${desc}" target="_blank">${label}</a></strong> / `

                        return this
                    },

                    create() {
                        return options.slice(0, -3)
                    }
                }
            }
        }
    }

    class SaveVideoSettings {
        static init() {
            let video = window.html5player.video || document.querySelector('#html5video video')

            class Settings {
                static getSettings() {
                    return JSON.parse(localStorage.getItem(LOCAL_VIDEO_SETTINGS))
                }

                static getItem(key) {
                    let settings = this.getSettings()

                    return settings[key]
                }

                static setItem(key, value) {
                    let settings = this.getSettings()

                    settings[key] = value

                    localStorage.setItem(LOCAL_VIDEO_SETTINGS, JSON.stringify(settings))
                }
            }

            class Player {
                static get volume() {
                    return video.volume
                }

                static set volume(volume) {
                    window.html5player.setVolume(volume)
                }

                static get isMuted() {
                    return video.muted
                }

                static mute() {
                    return window.html5player.mute()
                }

                static get quality() {
                    return window.html5player.qualitiesmenubuttonlabel
                }

                static set quality(quality) {
                    let qualityMenu = window.html5player.qualitymenu

                    if(qualityMenu) {
                        setQuality()
                    } else {
                        let obs = new MutationObserver(() => {
                            if(window.html5player.qualitymenu) {
                                qualityMenu = window.html5player.qualitymenu

                                if(qualityMenu) {
                                    setQuality()
                                    obs.disconnect()
                                }
                            }
                        })

                        obs.observe(document.body, {
                            childList: true,
                            subtree: true,
                        })
                    }

                    function setQuality() {
                        let qualityEls = qualityMenu.querySelectorAll('span')

                        for (let i = 0; i < qualityEls.length; i++) {
                            let qualityEl = qualityEls[i]

                            if(qualityEl.textContent.trim().toLowerCase() === quality.toLowerCase()) {
                                return qualityEl.parentNode.click()
                            }
                        }

                        log('Cannot find quality "' + quality + '"')
                    }
                }

                static get speed() {
                    return window.html5player.speed
                }

                static set speed(speed) {
                    window.html5player.speed = speed
                }

                static get isLooped() {
                    return window.html5player.loopbtn.querySelector('img[src*="1.svg"]') !== null
                }

                static loop() {
                    let isLooped = this.isLooped

                    if(!isLooped) {
                        window.html5player.loopbtn.click()
                    }
                }
            }

            restoreVideoSettings()

            const TARGET_NODE = document.querySelector('#hlsplayer') || document.body

            let obs = new MutationObserver((mutationList) => {
                for (let i = 0; i < mutationList.length; i++) {
                    const MUTATION_RECORD = mutationList[i]
                    const TARGET = MUTATION_RECORD.target

                    if(TARGET.matches('.buttons-bar *, .settings-menu *')) {
                        return setVideoSettings()
                    }
                }
            })

            obs.observe(TARGET_NODE, {
                childList: true,
                subtree: true,
            })

            function setVideoSettings() {
                const VOLUME = Player.volume
                const MUTED = Player.isMuted
                const QUALITY = Player.quality
                const SPEED = Player.speed
                const LOOPED = Player.isLooped

                Settings.setItem('volume', VOLUME)
                Settings.setItem('muted', MUTED)
                Settings.setItem('quality', QUALITY)
                Settings.setItem('speed', SPEED)
                Settings.setItem('loop', LOOPED)
            }

            function restoreVideoSettings() {
                Player.volume = Settings.getItem('volume')

                const WAS_MUTED = Settings.getItem('muted')

                if(WAS_MUTED) {
                    Player.mute()
                }

                Player.quality = Settings.getItem('quality')
                Player.speed = Settings.getItem('speed')

                const WAS_LOOPED = Settings.getItem('loop')

                if(WAS_LOOPED) {
                    Player.loop()
                }
            }
        }
    }

    class BetterVideoPage {
        static init() {
            const IS_VIDEO = isVideoPage()

            if(IS_VIDEO) {
                const MODULES = [
                    CopyableTitle,
                    FastButtons,
                    SaveVideoSettings
                ]

                initModules(MODULES)
            }
        }
    }

    class Main {
        static init() {
            if(!localStorage.getItem(LOCAL_VIDEO_SETTINGS)) {
                localStorage.setItem(LOCAL_VIDEO_SETTINGS, DEFAULT_SETTINGS)
            }

            const MODULES = [
                CSS,
                DarkThemeChecker,
                AntiAntiAdBlock,
                AdBlock,
                PlusSymbol,
                FastSearch,
                BetterVideoPage
            ]

            initModules(MODULES)

            log('Loaded')
        }
    }

    Main.init()

    function initModules(modules) {
        for (let i = 0; i < modules.length; i++) {
            const MODULE = modules[i]

            initModule(MODULE)
        }
    }

    function initModule(module) {
        try {
            module.init()
        } catch(e) {
            console.error(TITLE, module.name + ' module, has error:', e)
        }
    }

    function addStyle(css) {
        let styleNode = document.createElement('style')
        styleNode.appendChild(document.createTextNode(css))
        document.head.appendChild(styleNode)
    }

    function toggleVisibility(el) {
        if(el.style.display === 'none') {
            el.style.display = 'block'

            return void 0
        }

        el.style.display = 'none'
    }

    function log(msg) {
        const CSS_LOG_DEFAULT = 'background: rgb(0, 0, 0);font-weight: 800;padding: 1px;'

        console.log('%cX%cVIDEOS+',
            CSS_LOG_DEFAULT + 'color: rgb(222, 38, 0);',
            CSS_LOG_DEFAULT + 'color: rgb(255, 255, 255);margin-right: -5px;',
            ' -',
            msg
        )
    }

    function isVideoPage() {
        return location.pathname.match(/video\d/)
    }

    function search(q) {
        return replaceLocation(location.origin + '/?k=' + toSearchFormat(q))
    }

    function toSearchFormat(str) {
        return encodeURIComponent(str.trim()).replace(/%20+/g, '+')
    }

    function replaceLocation(newUrl) {
        if(location.replace) {
            return location.replace(newUrl)
        }

        location.href = newUrl
    }
})();