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