// ==UserScript==
// @name Porn Multi-Search
// @namespace -
// @version 1.0.6
// @description show options for search request where you can choose your favorite porn site search engine.
// @author NotYou
// @match *://*/*
// @include *
// @license GPL-3.0-or-later
// @grant none
// ==/UserScript==
(function() {
const COLUMNS = '3' // Amout of columns
const FAVICON = 'google' // You can also choose 'duckduckgo' instead of 'google'
const CUSTOM_ENGINES = [
{
name: 'Site Name Here', // site name that will be displayed
searchUrl: 'https://www.some-example.website/video/search?q=%s', // place search url here and replace your search request with "%s" (without quotes)
fixed: false, // write here "true" (without quotes) if navigation bar is always visible (fixed at screen), otherwise "false" (without quotes)
isExample: true, // do not include this property in your custom engine
},
// For Example:
{
name: 'EPorner',
searchUrl: 'https://www.eporner.com/search/%s/',
fixed: true,
},
]
let _engines = [
{
name: 'PornHub',
searchUrl: 'https://www.pornhub.com/video/search?search=%s',
input: '#searchInput',
},
{
name: 'PornHub (Premium)',
searchUrl: 'https://www.pornhubpremium.com/video/search?search=%s',
input: '#searchInput',
},
{
name: 'XVideos',
searchUrl: 'https://www.xvideos.com/?k=%s',
input: '#xv-search-form .search-input',
},
{
name: 'XNXX',
searchUrl: 'https://www.xnxx.com/search/%s',
input: '#k',
},
{
name: 'xHamster',
searchUrl: 'https://xhamster.com/search/%s',
input: '.search-text[name="q"]',
fixed: true,
},
{
name: 'RedTube',
searchUrl: 'https://www.redtube.com/?search=%s',
input: '#header_search_field',
fixed: true,
},
{
name: 'PornEZ',
searchUrl: 'https://pornez.cam/?s=%s',
input: '#s',
},
{
name: 'YouPorn',
searchUrl: 'https://www.youporn.com/search/?query=%s',
input: '#query',
fixed: true,
param: 'query',
},
{
name: 'SpankBang',
searchUrl: 'https://spankbang.com/s/%s/',
input: 'header ul.top > li.search form input',
},
{
name: 'WhoresHub',
searchUrl: 'https://www.whoreshub.com/search/%s/',
input: '[name="q"]',
},
{
name: 'PornTube',
searchUrl: 'https://www.porntube.com/search?q=%s',
input: '#searchText',
fixed: true,
},
{
name: 'HQPorner',
searchUrl: 'https://hqporner.com/?q=%s',
input: '#searchInput',
},
]
let engines = {}
let inputSelector = 'form :where([name="q"], [name="search"], [name="query"], [type="search"], [class*="search"], [id*="search"], #q, #query, #searchInput), form[action*="search"] input:not([type="submit"])'
_engines.forEach(e => {
e = Object.assign(e, {
domain: getDomain(e.searchUrl),
fixed: e.fixed ? true : false
})
engines[e.domain] = e
})
CUSTOM_ENGINES.forEach(e => {
e = Object.assign(e, {
domain: getDomain(e.searchUrl),
fixed: e.fixed ? true : false,
input: inputSelector,
})
engines[e.domain] = e
})
let engine = engines[getDomain()]
if(document.readyState === 'interactive' || document.readyState === 'complete') {
_init()
} else {
document.addEventListener('readystatechange', _init)
}
function _init() {
if(document.readyState === 'interactive' || document.readyState === 'complete') {
if(typeof engine !== 'undefined') {
let colors = getAccentColors()
let css = `
:root {
--pms-bg: ${colors.bg};
--pms-fg: ${colors.fg};
--pms-text: ${colors.text};
}
#multiSearch {
position: ${engine.fixed ? 'fixed' : 'absolute'};
padding: 10px;
z-index: 2147483647;
background-color: var(--pms-bg);
border-radius: 8px;
opacity: 0;
pointer-events: none;
transition: .3s;
border: 1px solid rgb(0, 0, 0);
box-sizing: content-box;
display: grid;
grid-template-columns: ${'auto '.repeat(COLUMNS ? +COLUMNS || COLUMNS.toString().length : 3)};
}
#multiSearch.active {
opacity: 1;
pointer-events: auto;
}
#multiSearch::before {
content: '';
width: 10px;
height: 10px;
display: block;
position: absolute;
top: -5px;
left: calc(50% - 5px);
rotate: -45deg;
background: var(--pms-bg);
border-right: 1px solid rgb(0, 0, 0);
border-top: 1px solid rgb(0, 0, 0);
}
#multiSearch:hover {
opacity: 1;
pointer-events: auto;
}
#multiSearch div {
padding: 5px;
margin: 3px;
align-items: center;
display: flex;
cursor: pointer;
border-radius: 10px;
transition: .3s;
}
#multiSearch div:hover {
background-color: var(--pms-fg);
}
#multiSearch img {
width: 32px;
height: 32px;
border-radius: 2px;
}
#multiSearch span {
margin-left: 5px;
color: var(--pms-text);
}`.replace(';', ' !important;')
document.head.insertAdjacentHTML('beforeend', `<style>${css.trim().replace(/\s\s/g, '')}</style>`)
init()
}
document.removeEventListener('readystatechange', _init, false)
}
}
function init() {
let input = document.querySelector(engine.input) || document.querySelector(inputSelector)
let fav = FAVICON ? FAVICON.toLowerCase() : 'google'
let html = `<div id="multiSearch" style="height: 0;">
${_engines.concat(CUSTOM_ENGINES).map(e => e.domain === getDomain() || e.isExample ? '' :
`<div data-search="${e.searchUrl}">
<img src="${getDomainIcon(e.domain)[fav]}" alt="${e.domain}">
<span>${e.name}</span>
</div>`
).join('')
}
</div>`
let timeout
let timeoutExit
document.body.insertAdjacentHTML('beforeend', html)
let multiSearch = document.querySelector('#multiSearch')
window.addEventListener('resize', setPosition)
input.addEventListener('mouseenter', onMouseEnter)
input.addEventListener('mouseout', onMouseOut)
input.addEventListener('input', onMouseOut)
multiSearch.addEventListener('mouseout', onMouseOut)
multiSearch.addEventListener('click', e => {
let t = e.target
t = t.tagName.toLowerCase() !== 'div'
? t.parentNode
: t.id === 'multiSearch'
? t.firstElementChild
: t
if(t.dataset.search) {
search(t)
}
})
function getQuery() {
return input.value || new URLSearchParams(location.search).get(engine.param) || ''
}
function search(target) {
let v = getQuery()
v = target.dataset.search.replace('%s', v)
open(v)
}
function open(url) {
let a = document.createElement('a')
a.target = '_blank'
a.href = url
a.click()
}
function onMouseEnter() {
clearTimeout(timeoutExit)
timeout = setTimeout(setPosition, 1e3)
multiSearch.className = 'active'
}
function onMouseOut() {
timeoutExit = setTimeout(hide, 200)
}
function setPosition() {
let bcr = input.getBoundingClientRect()
multiSearch.style.cssText = `
left: ${bcr.width / 2 + bcr.left - multiSearch.offsetWidth / 2}px;
top: ${bcr.top + bcr.height + 10}px;`
}
function hide() {
clearTimeout(timeout)
multiSearch.className = ''
}
}
function getAccentColors() {
let _bg = getBg(document.body)
let __bg = getBg(document.documentElement)
let bg = isTransparent(_bg)
? isTransparent(__bg)
? 'rgb(255, 255, 255)'
: __bg
: _bg
let fg = 'rgb(' + bg.slice(4, -1).split(', ').map(e => e > 128 ? e - 30 : +e + 30) + ')'
let text = reverseRgb(bg)
return {
bg,
fg,
text
}
function getBg(el) {
return window.getComputedStyle(el).backgroundColor
}
function isTransparent(color) {
return color === 'transparent' || color === 'rgba(0, 0, 0, 0)' || color === '#0000'
}
function reverseRgb(rgb) {
return 'rgb(' + rgb.slice(4, -1).split(', ').map(e => 255 - e).join(',') + ')'
}
}
function getDomainIcon(domain) {
return {
duckduckgo: `https://icons.duckduckgo.com/ip3/${domain}.ico`,
google: `https://www.google.com/s2/favicons?domain=${domain}&sz=256`,
}
}
function getDomain(url = location.href) {
return new URL(url).host
}
})()