PornHub+

Ad blocker, filters, no autoplay, fast search etc.

  1. // ==UserScript==
  2. // @name PornHub+
  3. // @namespace -
  4. // @version 1.6.1
  5. // @description Ad blocker, filters, no autoplay, fast search etc.
  6. // @author NotYou
  7. // @match *://pornhub.com/*
  8. // @match *://pornhubpremium.com/*
  9. // @match *://*.pornhub.com/*
  10. // @match *://*.pornhubpremium.com/*
  11. // @compatible Firefox Version 78
  12. // @compatible Chrome Version 88
  13. // @compatible Edge Version 88
  14. // @compatible Opera Version 74
  15. // @compatible Safari Version 14
  16. // @run-at document-end
  17. // @license GPL-3.0-or-later
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. const TITLE = 'PornHub+'
  23. const LOCAL_SETTINGS = 'settings_pp'
  24.  
  25. const DEFAULT_SETTINGS = JSON.stringify({
  26. stepLike: 0,
  27. incest: 0,
  28. nonHetero: 0,
  29. bdsm: 0
  30. })
  31.  
  32. const LABELS = {
  33. stepLike: 'Step-like',
  34. incest: 'Incest',
  35. nonHetero: 'Non-Hetero',
  36. bdsm: 'BDSM or Hardcore'
  37. }
  38.  
  39. class Settings {
  40. static getSettings() {
  41. return JSON.parse(localStorage.getItem(LOCAL_SETTINGS))
  42. }
  43.  
  44. static getItem(key) {
  45. const settings = this.getSettings()
  46.  
  47. return settings[key]
  48. }
  49.  
  50. static setItem(key, value) {
  51. const settings = this.getSettings()
  52.  
  53. settings[key] = value
  54.  
  55. localStorage.setItem(LOCAL_SETTINGS, JSON.stringify(settings))
  56. }
  57. }
  58.  
  59. class CSS {
  60. static init() {
  61. const _CSS = `
  62. #header.hasAdAlert {
  63. grid-template-rows: unset !important;
  64. }
  65.  
  66. #headerContainer {
  67. padding-bottom: 10px;
  68. }
  69.  
  70. #fast-search-pp {
  71. position: fixed;
  72. z-index: 2147483646;
  73. width: 400px;
  74. height: 40px;
  75. padding: 16px 28px 28px 16px;
  76. background-color: rgba(21, 21, 21, 0.57);
  77. border-radius: 3px;
  78. left: 32vw;
  79. top: 20vh;
  80. }
  81.  
  82. #fast-search-input-parent-pp {
  83. padding: 5px;
  84. display: flex;
  85. border: 0;
  86. height: 40px;
  87. width: 100%;
  88. background-color: rgb(61, 54, 49);
  89. border-radius: 3px;
  90. }
  91.  
  92. #fast-search-input-parent-pp .ph-icon-search {
  93. margin-top: 10px;
  94. margin-right: 10px;
  95. font-size: 20px;
  96. }
  97.  
  98. #fast-search-input-pp {
  99. background-color: unset;
  100. width: 100%;
  101. height: 100%;
  102. font-size: 20px;
  103. border: 0;
  104. }
  105.  
  106. #fast-search-input-pp::placeholder {
  107. font-style: italic;
  108. color: rgb(164, 164, 164);
  109. }
  110.  
  111. .active-category-pp a {
  112. color: rgb(255, 153, 0) !important;
  113. }`
  114.  
  115. addStyle(_CSS)
  116. }
  117. }
  118.  
  119. class ClearBtn {
  120. static init() {
  121. const NETWORK_LIST = document.querySelector('.networkListContent')
  122.  
  123. if(NETWORK_LIST) {
  124. const CLEAR_BTN = document.createElement('li')
  125. const CLEAR_PP = document.createElement('a')
  126. const ID = NETWORK_LIST.children.length
  127.  
  128. CLEAR_PP.className = 'networkTab'
  129. CLEAR_PP.href = 'javascript:;'
  130. CLEAR_PP.title = 'Clear watched videos, search requests from browser\'s local storage'
  131. CLEAR_PP.textContent = 'Clear'
  132. CLEAR_BTN.dataset.id = ID
  133.  
  134. CLEAR_BTN.addEventListener('click', clearStorage)
  135.  
  136. CLEAR_BTN.appendChild(CLEAR_PP)
  137. NETWORK_LIST.appendChild(CLEAR_BTN)
  138.  
  139. function clearStorage() {
  140. const STORAGE_KEYS = ['watchedVideoStorage', 'watchedVideoIds', 'currentTimeStamp', 'recentSearch']
  141.  
  142. for (let i = 0; i < STORAGE_KEYS.length; i++) {
  143. const STORAGE_KEY = STORAGE_KEYS[i]
  144.  
  145. localStorage.removeItem(STORAGE_KEY)
  146. }
  147. }
  148. }
  149. }
  150. }
  151.  
  152. class AdBlock {
  153. static init() {
  154. let css = ''
  155. const bodies = []
  156.  
  157. for (let i = 0; i < 10; i++) {
  158. bodies.push(`body > [class*=${i}]`)
  159. }
  160.  
  161. const SELECTORS = [
  162. '.cookiesBanner',
  163. '#pb_template',
  164. '.adsbytrafficjunky',
  165. '.adLinks',
  166. '.adLink',
  167. '#hd-rightColVideoPage > .clearfix:first-child',
  168. '#popsByTrafficJunky',
  169. bodies.join(','),
  170. '#videoSearchResult > :first-child:not(.userCardLi)',
  171. '#welcome',
  172. '#vpContentContainer > div:nth-child(2) > :first-child',
  173. '.video-wrapper > .hd.clear',
  174. '#js-abContainterMain',
  175. '.t8fer',
  176. '.t8fer k0g8',
  177. '#footerMenu_advertising',
  178. '.ad-link',
  179. 'body > *:not(:where(div, template, script, style, link, ins, noscript, iframe, img, pre))',
  180. '.carouselOverlay',
  181. '.cookiesBanner + * + *',
  182. `:where(#popularPornstars,
  183. .gifsWrapper > ul > :first-child,
  184. #hottestVideosSection,
  185. #photosAlbumsSection.withAd,
  186. #hotVideosSection,
  187. #videoCategory, .container > div[class=""],
  188. #videoSearchResult,
  189. #singleFeedSection) > :where(:first-child:not(:where(li, div)), .sniperModeEngaged)`,
  190. '#channelsBody .leftSide.floatLeft .rankWrapper + :not(.clearfix)',
  191. '.alpha ~ .emptyBlockSpace[style="display: block;"]',
  192. '.sponsor-text'
  193. ]
  194.  
  195. for (let i = 0; i < SELECTORS.length; i++) {
  196. const SELECTOR = SELECTORS[i]
  197.  
  198. css += SELECTOR + '{ display: none !important; }'
  199. }
  200.  
  201. addStyle(css)
  202. }
  203. }
  204.  
  205. class AdBlockAdvanced {
  206. static init() {
  207. removeAds()
  208.  
  209. const obs = new MutationObserver(removeAds)
  210. obs.observe(document.body, {
  211. childList: true,
  212. subtree: true,
  213. })
  214.  
  215. function removeAds() {
  216. const ADS_ELEMENTS = document.querySelectorAll('[data-embeddedads="true"]:not([style="all: unset !important; display: none !important;"])')
  217.  
  218. for (let i = 0; i < ADS_ELEMENTS.length; i++) {
  219. const ADS_ELEMENT = ADS_ELEMENTS[i]
  220. const ADS_PARENT_ELEMENT = ADS_ELEMENT.parentNode
  221. const IS_ADS_ELEMENT = ADS_PARENT_ELEMENT.children.length === 1 && ADS_PARENT_ELEMENT.className
  222.  
  223. if(IS_ADS_ELEMENT) {
  224. ADS_PARENT_ELEMENT.setAttribute('style', 'all: unset !important; display: none !important;')
  225. }
  226. }
  227. }
  228. }
  229. }
  230.  
  231. class PlusSymbol {
  232. static init() {
  233. const LOGO_WRAPPER = document.querySelector('.logo .logoWrapper')
  234. const PLUS = document.createElement('i')
  235. PLUS.style.marginTop = '10px'
  236. PLUS.style.fontSize = '20px'
  237. PLUS.className = 'ph-icon-add'
  238.  
  239. if(LOGO_WRAPPER) {
  240. LOGO_WRAPPER.style.display = 'flex'
  241. LOGO_WRAPPER.appendChild(PLUS)
  242. }
  243. }
  244. }
  245.  
  246. class NoAutoplay {
  247. static init() {
  248. const LOCAL_PLAYER_NAME = 'mgp_player'
  249. const LOCAL_PLAYER_SETTINGS = localStorage.getItem(LOCAL_PLAYER_NAME)
  250.  
  251. let playerSettings = JSON.parse(LOCAL_PLAYER_SETTINGS)
  252.  
  253. if(playerSettings) {
  254. playerSettings.autoplay = false
  255.  
  256. const PLAYER_SETTINGS = JSON.stringify(playerSettings)
  257.  
  258. localStorage.setItem(LOCAL_PLAYER_NAME, PLAYER_SETTINGS)
  259. }
  260. }
  261. }
  262.  
  263. class FastSearch {
  264. static init() {
  265. const FAST_SEARCH_EL = document.createElement('div')
  266. FAST_SEARCH_EL.id = 'fast-search-pp'
  267.  
  268. toggleVisibility(FAST_SEARCH_EL)
  269.  
  270. const FAST_SEARCH_PARENT_EL = document.createElement('span')
  271. FAST_SEARCH_PARENT_EL.id = 'fast-search-input-parent-pp'
  272.  
  273. const ICON = document.createElement('i')
  274. ICON.className = 'ph-icon-search'
  275.  
  276. const INPUT = document.createElement('input')
  277. INPUT.id = 'fast-search-input-pp'
  278. INPUT.placeholder = 'Fast Search Pornhub'
  279.  
  280. FAST_SEARCH_PARENT_EL.appendChild(ICON)
  281. FAST_SEARCH_PARENT_EL.appendChild(INPUT)
  282. FAST_SEARCH_EL.appendChild(FAST_SEARCH_PARENT_EL)
  283.  
  284. INPUT.addEventListener('keydown', fastSearchHandler)
  285. ICON.addEventListener('click', fastSearchHandler)
  286.  
  287. document.body.appendChild(FAST_SEARCH_EL)
  288.  
  289. window.addEventListener('keydown', e => {
  290. if((e.metaKey || e.ctrlKey) && e.code === 'KeyK') {
  291. e.preventDefault()
  292. toggleVisibility(FAST_SEARCH_EL)
  293. INPUT.focus()
  294. }
  295. })
  296.  
  297. function fastSearchHandler(e) {
  298. if((e.type === 'keydown' && e.code === 'Enter') || e.type === 'click') {
  299. const QUERY = getQuery()
  300.  
  301. search(QUERY)
  302. }
  303.  
  304. function search(query) {
  305. replaceLocation(location.origin + '/video/search?search=' + query)
  306. }
  307.  
  308. function getQuery() {
  309. const VALUE = INPUT.value
  310. const RE_UNUSED = /[^0-9a-zA-Z\\s-]+/g
  311. const VALUE_CLEAN = VALUE.replaceAll(RE_UNUSED, '').replaceAll(/\s/g, '+')
  312.  
  313. return VALUE_CLEAN
  314. }
  315. }
  316. }
  317. }
  318.  
  319. class FastButtons {
  320. static init() {
  321. const SEARCH_PARAMS = getSearchParams()
  322. const VIEWKEY = SEARCH_PARAMS.get('viewkey')
  323. const MENU_ROW = document.querySelector('#js-shareData > .tab-menu-wrapper-row')
  324. const MENU_CELL = document.createElement('div')
  325. MENU_CELL.className = 'tab-menu-wrapper-cell'
  326.  
  327. const MENU_CELL_LINK = document.createElement('a')
  328. MENU_CELL_LINK.href = location.origin + '/embed/' + VIEWKEY
  329. MENU_CELL_LINK.target = '_self'
  330.  
  331. const EMBED_BTN = document.createElement('div')
  332. EMBED_BTN.id = 'embed_mode-btn'
  333. EMBED_BTN.className = 'flag-btn tab-menu-item tooltipTrig'
  334. EMBED_BTN.dataset.title = 'Open video in embed mode'
  335. EMBED_BTN.setAttribute('role', 'button')
  336. EMBED_BTN.tabindex = 0
  337.  
  338. const EMBED_ICON = document.createElement('i')
  339. EMBED_ICON.className = 'ph-icon-channel'
  340.  
  341. const EMBED_TEXT = document.createElement('span')
  342. EMBED_TEXT.textContent = 'Embed'
  343.  
  344. EMBED_BTN.appendChild(EMBED_ICON)
  345. EMBED_BTN.appendChild(EMBED_TEXT)
  346. MENU_CELL_LINK.appendChild(EMBED_BTN)
  347. MENU_CELL.appendChild(MENU_CELL_LINK)
  348. MENU_ROW.appendChild(MENU_CELL)
  349. }
  350. }
  351.  
  352. class CopyableTitle {
  353. static init() {
  354. const TITLE_EL = document.querySelector('.title-container > .title > span')
  355. TITLE_EL.title = 'Click to copy'
  356. TITLE_EL.addEventListener('click', () => {
  357. navigator.clipboard.writeText(TITLE_EL.innerText)
  358. })
  359. }
  360. }
  361.  
  362. class BetterVideoPage {
  363. static init() {
  364. const IS_VIDEO = isVideoPage()
  365.  
  366. if(IS_VIDEO) {
  367. const MODULES = [
  368. FastButtons,
  369. CopyableTitle
  370. ]
  371.  
  372. initModules(MODULES)
  373. }
  374. }
  375. }
  376.  
  377. class Filters {
  378. static init() {
  379. const SEARCH_PARAMS = getSearchParams()
  380. const IS_VALID_PAGE = SEARCH_PARAMS.has('search') ||
  381. SEARCH_PARAMS.has('page') ||
  382. SEARCH_PARAMS.has('o') ||
  383. (location.pathname === '/video' && SEARCH_PARAMS.toString() === '') ||
  384. location.pathname.indexOf('categories/') !== -1
  385.  
  386. if(IS_VALID_PAGE) {
  387. const FILTERS = {
  388. stepLike: [
  389. 'stepmom', 'stepmother', 'stepmommy', 'stepmum', 'step-mom', 'step-mother', 'step-mum', 'step-mommy', 'step mom', 'step mother', 'step mum', 'step mommy',
  390. 'stepdad', 'step-dad', 'step dad',
  391. 'stepson', 'step-son', 'step son',
  392. 'stepsis', 'stepsister', 'step-sister', 'step-sis', 'step sister', 'step sis',
  393. 'stepdaughter', 'step-daughter', 'step daughter'
  394. ],
  395. incest: [
  396. 'mom', 'mother', 'mommy', 'milf',
  397. 'dad', 'father', 'daddy',
  398. 'bro', 'brother',
  399. 'sis', 'sister',
  400. 'daughter',
  401. 'son',
  402.  
  403. // Series
  404.  
  405. 'mylf', 'gotmylf', 'mylfdom', 'badmilfs', 'bad milfs', 'analmom', 'anal mom', 'pervmom', 'perv mom', 'milfbody', 'pervnana', 'perv nana', 'momswap', 'momshoot', 'momsteachsex', 'mommy\'s girl',
  406. 'sis loves me', 'brattysis', 'sisswap', 'tinysis',
  407. 'dad crush',
  408. 'not my grandpa', 'notmygrandpa',
  409. 'spyfam', 'spy fam', 'family sinners', 'myfamilypies', 'familystrokes', 'family strokes'
  410. ],
  411. nonHetero: [
  412. 'lesbian',
  413. 'gay', 'homo', 'homosexual',
  414. 'bi', 'bisexual',
  415. 'trans', 'transgender',
  416.  
  417. // Series
  418.  
  419. 'helloladyboy', 'mommy\'s girl', 'girlsway', 'the lesbian experience', 'all girl massage',
  420. ],
  421. bdsm: [
  422. 'hardcore',
  423. 'bdsm',
  424. 'biting',
  425. 'scratching',
  426. 'dom', 'femdom', 'maledom', 'domination',
  427. 'wax', 'waxing',
  428. 'gag', 'gagging',
  429. 'whip', 'whiping',
  430. 'tied', 'tying',
  431. 'bondage'
  432. ]
  433. }
  434.  
  435. let appliedFilters = {}
  436.  
  437. const HEADER = createSidebarHeader('Filter', 'filter')
  438. const CATEGORIES = createCategories()
  439.  
  440. const FILTERS_KEYS = Object.keys(FILTERS)
  441.  
  442. for (let i = 0; i < FILTERS_KEYS.length; i++) {
  443. const FILTER_KEY = FILTERS_KEYS[i]
  444. const LABEL = LABELS[FILTER_KEY]
  445.  
  446. CATEGORIES.createCategory(LABEL, FILTER_KEY, 'filter')
  447. }
  448.  
  449. const FILTER_CATEGORIES = CATEGORIES.create()
  450.  
  451. const TO_INSERT_ELS = [
  452. HEADER,
  453. FILTER_CATEGORIES
  454. ]
  455. const DURATION_WRAPPER = document.querySelector('#duration-wrapper')
  456. const DURATION_HEADER = DURATION_WRAPPER.previousElementSibling
  457.  
  458. for (let i = 0; i < TO_INSERT_ELS.length; i++) {
  459. const TO_INSERT_EL = TO_INSERT_ELS[i]
  460.  
  461. DURATION_HEADER.insertAdjacentElement('beforebegin', TO_INSERT_EL)
  462. }
  463.  
  464. function createCategories() {
  465. const WRAPPER = document.createElement('div')
  466. WRAPPER.className = 'sidebar_wrapper'
  467.  
  468. const CATEGORIES = document.createElement('ul')
  469. CATEGORIES.className = 'nf-categories'
  470.  
  471. WRAPPER.appendChild(CATEGORIES)
  472.  
  473. return {
  474. createCategory,
  475. create
  476. }
  477.  
  478. function createCategory(label, category, type) {
  479. const CATEGORY = document.createElement('li')
  480. CATEGORY.className = 'sidebarIndent'
  481.  
  482. const IS_ENABLED_BY_DEFAULT = Boolean(Settings.getItem(category))
  483.  
  484. appliedFilters[category] = IS_ENABLED_BY_DEFAULT
  485.  
  486. if(IS_ENABLED_BY_DEFAULT) {
  487. CATEGORY.classList.add('active-category-pp')
  488. changeVideosVisibility(FILTERS[category], appliedFilters[category])
  489. }
  490.  
  491.  
  492. CATEGORY.addEventListener('click', () => {
  493. const IS_ENABLED = !Settings.getItem(category)
  494.  
  495. Settings.setItem(category, IS_ENABLED ? 1 : 0)
  496.  
  497. appliedFilters[category] = IS_ENABLED
  498.  
  499. CATEGORY.classList[IS_ENABLED ? 'add' : 'remove']('active-category-pp')
  500.  
  501. changeVideosVisibility(FILTERS[category], appliedFilters[category])
  502. })
  503.  
  504. const CATEGORY_LINK = document.createElement('a')
  505. CATEGORY_LINK.className = 'sidebarIndent'
  506. CATEGORY_LINK.href = 'javascript:;'
  507.  
  508. const CATEGORY_TEXT = document.createTextNode(label)
  509.  
  510. CATEGORY_LINK.appendChild(CATEGORY_TEXT)
  511. CATEGORY.appendChild(CATEGORY_LINK)
  512. CATEGORIES.appendChild(CATEGORY)
  513.  
  514. return this
  515. }
  516.  
  517. function create() {
  518. return WRAPPER
  519. }
  520. }
  521.  
  522. function createSidebarHeader(text, icon) {
  523. const SECTION_HEADER = document.createElement('div')
  524. SECTION_HEADER.className = 'section_bar_sidebar'
  525.  
  526. const SECTION_TITLE = document.createElement('div')
  527. SECTION_TITLE.className = 'section_title'
  528.  
  529. const ICON = document.createElement('i')
  530. ICON.className = 'ph-icon-' + icon
  531.  
  532. const TEXT = document.createTextNode(' ' + text + ' ')
  533.  
  534. const SUBTITLE = document.createElement('span')
  535. SUBTITLE.className = 'subtitle'
  536. SUBTITLE.textContent = '( PornHub+ )'
  537.  
  538. SECTION_TITLE.appendChild(ICON)
  539. SECTION_TITLE.appendChild(TEXT)
  540. SECTION_TITLE.appendChild(SUBTITLE)
  541. SECTION_HEADER.appendChild(SECTION_TITLE)
  542.  
  543. return SECTION_HEADER
  544. }
  545.  
  546. function changeVideosVisibility(filter, isHiding) {
  547. const VIDEO_BOXES = document.querySelectorAll('.videoBox')
  548.  
  549. for (let i = 0; i < VIDEO_BOXES.length; i++) {
  550. const VIDEO_BOX = VIDEO_BOXES[i]
  551. const TITLE = VIDEO_BOX.querySelector('.thumbnail-info-wrapper > .title a').title.toLowerCase().trim()
  552.  
  553. for (let j = 0; j < filter.length; j++) {
  554. const CURRENT_WORD = filter[j]
  555. const WORD_RE = createRegExp(CURRENT_WORD)
  556. const IS_VALID = WORD_RE.test(TITLE)
  557.  
  558. if(isHiding && IS_VALID) {
  559. VIDEO_BOX.style.display = 'none'
  560. } else if(!isHiding && !IS_VALID) {
  561. VIDEO_BOX.style.display = ''
  562. }
  563. }
  564. }
  565. }
  566.  
  567. function createRegExp(word) {
  568. return new RegExp(`^${word}$|^${word}|${word}$|\\s${word}\\s`)
  569. }
  570. }
  571. }
  572. }
  573.  
  574. class ChangeStars {
  575. static init() {
  576. const IS_VALID_PAGE = location.pathname.includes('/pornstar/') || location.pathname.includes('/model/')
  577.  
  578. if(IS_VALID_PAGE) {
  579. window.addEventListener('keydown', e => {
  580. if(e.shiftKey && (e.key === 'ArrowRight' || e.key === 'ArrowLeft')) {
  581. e.preventDefault()
  582.  
  583. const TYPE_OF_ARROW = e.key === 'ArrowRight' ? '.pornstarPrev' : '.pornstarNext'
  584. const ARROW = document.querySelector('.pornstarsNavButtons > ' + TYPE_OF_ARROW)
  585.  
  586. ARROW.click()
  587. }
  588. })
  589. }
  590. }
  591. }
  592.  
  593. class Main {
  594. static init() {
  595. if(!localStorage.getItem(LOCAL_SETTINGS)) {
  596. localStorage.setItem(LOCAL_SETTINGS, DEFAULT_SETTINGS)
  597. }
  598.  
  599. const MODULES = [
  600. CSS,
  601. ClearBtn,
  602. AdBlock,
  603. AdBlockAdvanced,
  604. PlusSymbol,
  605. NoAutoplay,
  606. FastSearch,
  607. BetterVideoPage,
  608. Filters,
  609. ChangeStars
  610. ]
  611.  
  612. initModules(MODULES)
  613.  
  614. log('Loaded')
  615. }
  616. }
  617.  
  618. Main.init()
  619.  
  620. function initModules(modules) {
  621. for (let i = 0; i < modules.length; i++) {
  622. const MODULE = modules[i]
  623.  
  624. initModule(MODULE)
  625. }
  626. }
  627.  
  628. function initModule(module) {
  629. try {
  630. module.init()
  631. } catch(e) {
  632. console.error(TITLE, module.name + ' module, has error:', e)
  633. }
  634. }
  635.  
  636. function toggleVisibility(el) {
  637. if(el.style.display === 'none') {
  638. el.style.display = 'block'
  639.  
  640. return void 0
  641. }
  642.  
  643. el.style.display = 'none'
  644. }
  645.  
  646. function log(msg) {
  647. console.log(
  648. 'Porn%cHub%c+',
  649. 'background: #f90;color: #000;border-radius: 3px;padding: 1px;',
  650. '-',
  651. msg
  652. )
  653. }
  654.  
  655. function getSearchParams() {
  656. return new URLSearchParams(location.search)
  657. }
  658.  
  659. function isVideoPage() {
  660. const SEARCH_PARAMS = getSearchParams()
  661.  
  662. return SEARCH_PARAMS.has('viewkey')
  663. }
  664.  
  665. function addStyle(css) {
  666. const styleNode = document.createElement('style')
  667. styleNode.appendChild(document.createTextNode(css))
  668. document.head.appendChild(styleNode)
  669. }
  670.  
  671. function replaceLocation(newUrl) {
  672. if(location.replace) {
  673. return location.replace(newUrl)
  674. }
  675.  
  676. location.href = newUrl
  677. }
  678. })()