Porn Multi-Search

show options for search request where you can choose your favorite porn site search engine.

  1. // ==UserScript==
  2. // @name Porn Multi-Search
  3. // @namespace -
  4. // @version 1.0.6
  5. // @description show options for search request where you can choose your favorite porn site search engine.
  6. // @author NotYou
  7. // @match *://*/*
  8. // @include *
  9. // @license GPL-3.0-or-later
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. const COLUMNS = '3' // Amout of columns
  15. const FAVICON = 'google' // You can also choose 'duckduckgo' instead of 'google'
  16. const CUSTOM_ENGINES = [
  17. {
  18. name: 'Site Name Here', // site name that will be displayed
  19. searchUrl: 'https://www.some-example.website/video/search?q=%s', // place search url here and replace your search request with "%s" (without quotes)
  20. fixed: false, // write here "true" (without quotes) if navigation bar is always visible (fixed at screen), otherwise "false" (without quotes)
  21. isExample: true, // do not include this property in your custom engine
  22. },
  23. // For Example:
  24. {
  25. name: 'EPorner',
  26. searchUrl: 'https://www.eporner.com/search/%s/',
  27. fixed: true,
  28. },
  29. ]
  30.  
  31. let _engines = [
  32. {
  33. name: 'PornHub',
  34. searchUrl: 'https://www.pornhub.com/video/search?search=%s',
  35. input: '#searchInput',
  36. },
  37. {
  38. name: 'PornHub (Premium)',
  39. searchUrl: 'https://www.pornhubpremium.com/video/search?search=%s',
  40. input: '#searchInput',
  41. },
  42. {
  43. name: 'XVideos',
  44. searchUrl: 'https://www.xvideos.com/?k=%s',
  45. input: '#xv-search-form .search-input',
  46. },
  47. {
  48. name: 'XNXX',
  49. searchUrl: 'https://www.xnxx.com/search/%s',
  50. input: '#k',
  51. },
  52. {
  53. name: 'xHamster',
  54. searchUrl: 'https://xhamster.com/search/%s',
  55. input: '.search-text[name="q"]',
  56. fixed: true,
  57. },
  58. {
  59. name: 'RedTube',
  60. searchUrl: 'https://www.redtube.com/?search=%s',
  61. input: '#header_search_field',
  62. fixed: true,
  63. },
  64. {
  65. name: 'PornEZ',
  66. searchUrl: 'https://pornez.cam/?s=%s',
  67. input: '#s',
  68. },
  69. {
  70. name: 'YouPorn',
  71. searchUrl: 'https://www.youporn.com/search/?query=%s',
  72. input: '#query',
  73. fixed: true,
  74. param: 'query',
  75. },
  76. {
  77. name: 'SpankBang',
  78. searchUrl: 'https://spankbang.com/s/%s/',
  79. input: 'header ul.top > li.search form input',
  80. },
  81. {
  82. name: 'WhoresHub',
  83. searchUrl: 'https://www.whoreshub.com/search/%s/',
  84. input: '[name="q"]',
  85. },
  86. {
  87. name: 'PornTube',
  88. searchUrl: 'https://www.porntube.com/search?q=%s',
  89. input: '#searchText',
  90. fixed: true,
  91. },
  92. {
  93. name: 'HQPorner',
  94. searchUrl: 'https://hqporner.com/?q=%s',
  95. input: '#searchInput',
  96. },
  97. ]
  98.  
  99. let engines = {}
  100. 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"])'
  101.  
  102. _engines.forEach(e => {
  103. e = Object.assign(e, {
  104. domain: getDomain(e.searchUrl),
  105. fixed: e.fixed ? true : false
  106. })
  107. engines[e.domain] = e
  108. })
  109.  
  110. CUSTOM_ENGINES.forEach(e => {
  111. e = Object.assign(e, {
  112. domain: getDomain(e.searchUrl),
  113. fixed: e.fixed ? true : false,
  114. input: inputSelector,
  115. })
  116. engines[e.domain] = e
  117. })
  118.  
  119. let engine = engines[getDomain()]
  120.  
  121. if(document.readyState === 'interactive' || document.readyState === 'complete') {
  122. _init()
  123. } else {
  124. document.addEventListener('readystatechange', _init)
  125. }
  126.  
  127. function _init() {
  128. if(document.readyState === 'interactive' || document.readyState === 'complete') {
  129. if(typeof engine !== 'undefined') {
  130. let colors = getAccentColors()
  131. let css = `
  132. :root {
  133. --pms-bg: ${colors.bg};
  134. --pms-fg: ${colors.fg};
  135. --pms-text: ${colors.text};
  136. }
  137.  
  138. #multiSearch {
  139. position: ${engine.fixed ? 'fixed' : 'absolute'};
  140. padding: 10px;
  141. z-index: 2147483647;
  142. background-color: var(--pms-bg);
  143. border-radius: 8px;
  144. opacity: 0;
  145. pointer-events: none;
  146. transition: .3s;
  147. border: 1px solid rgb(0, 0, 0);
  148. box-sizing: content-box;
  149. display: grid;
  150. grid-template-columns: ${'auto '.repeat(COLUMNS ? +COLUMNS || COLUMNS.toString().length : 3)};
  151. }
  152.  
  153. #multiSearch.active {
  154. opacity: 1;
  155. pointer-events: auto;
  156. }
  157.  
  158. #multiSearch::before {
  159. content: '';
  160. width: 10px;
  161. height: 10px;
  162. display: block;
  163. position: absolute;
  164. top: -5px;
  165. left: calc(50% - 5px);
  166. rotate: -45deg;
  167. background: var(--pms-bg);
  168. border-right: 1px solid rgb(0, 0, 0);
  169. border-top: 1px solid rgb(0, 0, 0);
  170. }
  171.  
  172. #multiSearch:hover {
  173. opacity: 1;
  174. pointer-events: auto;
  175. }
  176.  
  177. #multiSearch div {
  178. padding: 5px;
  179. margin: 3px;
  180. align-items: center;
  181. display: flex;
  182. cursor: pointer;
  183. border-radius: 10px;
  184. transition: .3s;
  185. }
  186.  
  187. #multiSearch div:hover {
  188. background-color: var(--pms-fg);
  189. }
  190.  
  191. #multiSearch img {
  192. width: 32px;
  193. height: 32px;
  194. border-radius: 2px;
  195. }
  196.  
  197. #multiSearch span {
  198. margin-left: 5px;
  199. color: var(--pms-text);
  200. }`.replace(';', ' !important;')
  201. document.head.insertAdjacentHTML('beforeend', `<style>${css.trim().replace(/\s\s/g, '')}</style>`)
  202. init()
  203. }
  204. document.removeEventListener('readystatechange', _init, false)
  205. }
  206. }
  207.  
  208. function init() {
  209. let input = document.querySelector(engine.input) || document.querySelector(inputSelector)
  210. let fav = FAVICON ? FAVICON.toLowerCase() : 'google'
  211.  
  212. let html = `<div id="multiSearch" style="height: 0;">
  213. ${_engines.concat(CUSTOM_ENGINES).map(e => e.domain === getDomain() || e.isExample ? '' :
  214. `<div data-search="${e.searchUrl}">
  215. <img src="${getDomainIcon(e.domain)[fav]}" alt="${e.domain}">
  216. <span>${e.name}</span>
  217. </div>`
  218. ).join('')
  219. }
  220. </div>`
  221. let timeout
  222. let timeoutExit
  223.  
  224. document.body.insertAdjacentHTML('beforeend', html)
  225.  
  226. let multiSearch = document.querySelector('#multiSearch')
  227.  
  228. window.addEventListener('resize', setPosition)
  229.  
  230. input.addEventListener('mouseenter', onMouseEnter)
  231. input.addEventListener('mouseout', onMouseOut)
  232. input.addEventListener('input', onMouseOut)
  233. multiSearch.addEventListener('mouseout', onMouseOut)
  234.  
  235. multiSearch.addEventListener('click', e => {
  236. let t = e.target
  237. t = t.tagName.toLowerCase() !== 'div'
  238. ? t.parentNode
  239. : t.id === 'multiSearch'
  240. ? t.firstElementChild
  241. : t
  242.  
  243. if(t.dataset.search) {
  244. search(t)
  245. }
  246. })
  247.  
  248. function getQuery() {
  249. return input.value || new URLSearchParams(location.search).get(engine.param) || ''
  250. }
  251.  
  252. function search(target) {
  253. let v = getQuery()
  254.  
  255. v = target.dataset.search.replace('%s', v)
  256.  
  257. open(v)
  258. }
  259.  
  260. function open(url) {
  261. let a = document.createElement('a')
  262. a.target = '_blank'
  263. a.href = url
  264. a.click()
  265. }
  266.  
  267. function onMouseEnter() {
  268. clearTimeout(timeoutExit)
  269. timeout = setTimeout(setPosition, 1e3)
  270. multiSearch.className = 'active'
  271. }
  272.  
  273. function onMouseOut() {
  274. timeoutExit = setTimeout(hide, 200)
  275. }
  276.  
  277. function setPosition() {
  278. let bcr = input.getBoundingClientRect()
  279.  
  280. multiSearch.style.cssText = `
  281. left: ${bcr.width / 2 + bcr.left - multiSearch.offsetWidth / 2}px;
  282. top: ${bcr.top + bcr.height + 10}px;`
  283. }
  284.  
  285. function hide() {
  286. clearTimeout(timeout)
  287. multiSearch.className = ''
  288. }
  289. }
  290.  
  291. function getAccentColors() {
  292. let _bg = getBg(document.body)
  293. let __bg = getBg(document.documentElement)
  294.  
  295. let bg = isTransparent(_bg)
  296. ? isTransparent(__bg)
  297. ? 'rgb(255, 255, 255)'
  298. : __bg
  299. : _bg
  300.  
  301. let fg = 'rgb(' + bg.slice(4, -1).split(', ').map(e => e > 128 ? e - 30 : +e + 30) + ')'
  302. let text = reverseRgb(bg)
  303.  
  304. return {
  305. bg,
  306. fg,
  307. text
  308. }
  309.  
  310. function getBg(el) {
  311. return window.getComputedStyle(el).backgroundColor
  312. }
  313.  
  314. function isTransparent(color) {
  315. return color === 'transparent' || color === 'rgba(0, 0, 0, 0)' || color === '#0000'
  316. }
  317.  
  318. function reverseRgb(rgb) {
  319. return 'rgb(' + rgb.slice(4, -1).split(', ').map(e => 255 - e).join(',') + ')'
  320. }
  321. }
  322.  
  323. function getDomainIcon(domain) {
  324. return {
  325. duckduckgo: `https://icons.duckduckgo.com/ip3/${domain}.ico`,
  326. google: `https://www.google.com/s2/favicons?domain=${domain}&sz=256`,
  327. }
  328. }
  329.  
  330. function getDomain(url = location.href) {
  331. return new URL(url).host
  332. }
  333. })()
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.