PH - Search & UI Tweaks

Various search filters and user experience enhancers

  1. // ==UserScript==
  2. // @name PH - Search & UI Tweaks
  3. // @namespace brazenvoid
  4. // @version 3.6.4
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Various search filters and user experience enhancers
  8. // @match https://*.pornhub.com/*
  9. // @match https://*.pornhub.org/*
  10. // @match https://*.pornhubpremium.com/*
  11. // @match https://*.pornhubpremium.org/*
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
  13. // @require https://greasyfork.org/scripts/375557-base-brazen-resource/code/Base%20Brazen%20Resource.js?version=1115796
  14. // @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=1115813
  15. // @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=1163542
  16. // @require https://greasyfork.org/scripts/429587-brazen-item-attributes-resolver/code/Brazen%20Item%20Attributes%20Resolver.js?version=1139392
  17. // @require https://greasyfork.org/scripts/424516-brazen-subscriptions-loader/code/Brazen%20Subscriptions%20Loader.js?version=1114774
  18. // @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=1166308
  19. // @grant GM_addStyle
  20. // @run-at document-end
  21. // ==/UserScript==
  22.  
  23. GM_addStyle(`#settings-wrapper{top:5vh;width:270px}.bg-brand{background-color:#ffa31a}.font-primary{color:black}.font-secondary{color:black}`)
  24.  
  25. // Environment
  26.  
  27. const PAGE_PATH_NAME = window.location.pathname
  28.  
  29. const IS_FEED_PAGE = PAGE_PATH_NAME.startsWith('/feeds')
  30. const IS_PLAYLIST_PAGE = PAGE_PATH_NAME.startsWith('/playlist')
  31. const IS_PROFILE_PAGE = PAGE_PATH_NAME.startsWith('/model') || PAGE_PATH_NAME.startsWith('/channels') || PAGE_PATH_NAME.startsWith('/user') ||
  32. PAGE_PATH_NAME.startsWith('/pornstar')
  33. const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/view_video')
  34. const IS_VIDEO_SEARCH_PAGE = PAGE_PATH_NAME.startsWith('/video') || PAGE_PATH_NAME.startsWith('/categories')
  35.  
  36. // Filters and configuration
  37.  
  38. const FILTER_PAID_VIDEOS = 'Hide Paid Videos'
  39. const FILTER_PREMIUM_VIDEOS = 'Hide Premium Videos'
  40. const FILTER_PRO_CHANNEL_VIDEOS = 'Hide Pro Channel Videos'
  41. const FILTER_PRIVATE_VIDEOS = 'Hide Private Videos'
  42. const FILTER_RECOMMENDED_VIDEOS = 'Hide Recommended Videos'
  43. const FILTER_VIDEOS_VIEWS = 'Views'
  44. const FILTER_USER = 'User Blacklist'
  45. const FILTER_WATCHED_VIDEOS = 'Watched Filters'
  46.  
  47. const LINK_DISABLE_PLAYLIST_CONTROLS = 'Disable Playlist Controls'
  48. const LINK_USER_PUBLIC_VIDEOS = 'User Public Videos'
  49.  
  50. const UI_AUTO_NEXT = 'Auto Next'
  51. const UI_LARGE_PLAYER_ALWAYS = 'Always Enlarge Player'
  52. const UI_REMOVE_IFRAMES = 'Remove Ad IFrames'
  53. const UI_REMOVE_LIVE_MODELS_SECTIONS = 'Remove Live Models Sections'
  54. const UI_REMOVE_PORN_STAR_SECTIONS = 'Remove Porn Star Sections'
  55.  
  56. class PHSearchAndUITweaks extends BrazenBaseSearchEnhancer
  57. {
  58. constructor()
  59. {
  60. super({
  61. isUserLoggedIn: $('#topRightProfileMenu').length > 0,
  62. itemDeepAnalysisSelector: '.video-wrapper',
  63. itemLinkSelector: '.title > a',
  64. itemListSelectors: 'ul.videos',
  65. itemNameSelector: '.title > a',
  66. itemSelectors: '.videoblock',
  67. requestDelay: 0,
  68. scriptPrefix: 'ph-sui-',
  69. })
  70.  
  71. this._configurationManager
  72. .addFlagField(FILTER_PAID_VIDEOS, 'Hide paid videos.')
  73. .addFlagField(FILTER_PREMIUM_VIDEOS, 'Hide premium videos.')
  74. .addFlagField(FILTER_PRIVATE_VIDEOS, 'Hide private Videos.')
  75. .addFlagField(FILTER_PRO_CHANNEL_VIDEOS, 'Hide videos from professional channels.')
  76. .addFlagField(FILTER_RECOMMENDED_VIDEOS, 'Hide recommended videos.')
  77. .addFlagField(LINK_DISABLE_PLAYLIST_CONTROLS, 'Disable playlist controls on video pages.')
  78. .addFlagField(LINK_USER_PUBLIC_VIDEOS, 'Jump directly to public videos on any profile link click.')
  79. .addFlagField(UI_AUTO_NEXT, 'Automatically go to next search page if no videos match after first run.')
  80. .addFlagField(UI_LARGE_PLAYER_ALWAYS, 'Enlarges player on all video pages.')
  81. .addFlagField(UI_REMOVE_IFRAMES, 'Removes all ad iframes.')
  82. .addFlagField(UI_REMOVE_LIVE_MODELS_SECTIONS, 'Remove live model stream sections from search.')
  83. .addFlagField(UI_REMOVE_PORN_STAR_SECTIONS, 'Remove porn star listings from search.')
  84. .addRadiosGroup(FILTER_WATCHED_VIDEOS, [
  85. ['No Filtering', 0],
  86. ['Hide Watched Videos', 1],
  87. ['Show Only Watched Videos', 2],
  88. ], 'Control fate of already watched videos.')
  89. .addRangeField(FILTER_VIDEOS_VIEWS, 0, 10000000, 'Filter videos by view count.')
  90. .addRulesetField(FILTER_USER, 6, 'Hides videos from specified users/channels.')
  91.  
  92. this._itemAttributesResolver
  93. .addAttribute(FILTER_PAID_VIDEOS, (item) => Validator.isChildMissing(item, '.p2v-icon, .fanClubVideoWrapper'))
  94. .addAttribute(FILTER_PREMIUM_VIDEOS, (item) => Validator.isChildMissing(item, '.marker-overlays > .premiumIcon'))
  95. .addAttribute(FILTER_PRIVATE_VIDEOS, (item) => Validator.isChildMissing(item, '.privateOverlay'))
  96. .addAttribute(FILTER_PRO_CHANNEL_VIDEOS, (item) => Validator.isChildMissing(item, '.channel-icon'))
  97. .addAttribute(FILTER_RECOMMENDED_VIDEOS, (item) => Validator.isChildMissing(item, '.recommendedFor'))
  98. .addAttribute(FILTER_USER, (item) => item.find('.usernameWrap a').attr('title'))
  99. .addAttribute(FILTER_VIDEOS_VIEWS, (item) => {
  100. let viewsCountString = item.find('.views var').text()
  101. let viewsCountMultiplier = 1
  102. let viewsCountStringLength = viewsCountString.length
  103.  
  104. if (viewsCountString[viewsCountStringLength - 1] === 'K') {
  105. viewsCountMultiplier = 1000
  106. viewsCountString = viewsCountString.replace('K', '')
  107. } else {
  108. if (viewsCountString[viewsCountStringLength - 1] === 'M') {
  109. viewsCountMultiplier = 1000000
  110. viewsCountString = viewsCountString.replace('M', '')
  111. }
  112. }
  113. return parseFloat(viewsCountString) * viewsCountMultiplier
  114. })
  115. .addAttribute(FILTER_WATCHED_VIDEOS, (item) => Validator.doesChildExist(item, '.watchedVideoText'))
  116.  
  117. this._setupSubscriptionLoader().addConfig({
  118. url: window.location.origin + $('#profileMenuDropdown > li > span > a').first().attr('href') + '/subscriptions',
  119. getPageCount: (page) => parseInt(page.children().first().text().replace(REGEX_PRESERVE_NUMBERS, '')) / 100,
  120. getPageUrl: (baseUrl, pageNo) => baseUrl + '?page=' + pageNo + ' .userWidgetWrapperGrid',
  121. subscriptionsCountSelector: '.profileContentLeft .showingInfo',
  122. subscriptionNameSelector: 'a.usernameLink',
  123. })
  124.  
  125. this._setupUI()
  126. this._setupCompliance()
  127. this._setupComplianceFilters()
  128. }
  129.  
  130. /**
  131. * Automatic next search page
  132. * @private
  133. */
  134. _autoNext()
  135. {
  136. let allVideos = $('.nf-videos ' + this._config.itemSelectors)
  137. if (allVideos.length > 0 && !allVideos.is(':visible')) {
  138. let nextButton = $('.page_next:not(.disabled) > a')
  139. if (nextButton.length) {
  140. window.location = nextButton.attr('href')
  141. }
  142. }
  143. }
  144.  
  145. /**
  146. * Changes profile links to directly point to public video listings
  147. * @private
  148. */
  149. _complyProfileLinks()
  150. {
  151. $('.usernameBadgesWrapper a, a.usernameLink, .usernameWrap a').each((index, profileLink) => {
  152. profileLink = $(profileLink)
  153. let href = profileLink.attr('href')
  154. if (href.startsWith('/channels') || href.startsWith('/model')) {
  155. profileLink.attr('href', href + '/videos')
  156. } else {
  157. if (href.startsWith('/user')) {
  158. profileLink.attr('href', href + '/videos/public')
  159. }
  160. }
  161. })
  162. }
  163.  
  164. /**
  165. * @private
  166. */
  167. _enlargePlayer()
  168. {
  169. let player = $('#player')
  170. if (player.hasClass('original')) {
  171. player.removeClass('original').addClass('wide')
  172. }
  173. }
  174.  
  175. /**
  176. * Fixes left over space after ads removal
  177. * @private
  178. */
  179. _fixLeftOverSpaceOnVideoSearchPage()
  180. {
  181. $('.showingCounter, .tagsForWomen').each((index, div) => {
  182. div.style.height = 'auto'
  183. })
  184. }
  185.  
  186. /**
  187. * Fixes pagination nav by moving it under video items list
  188. * @private
  189. */
  190. _fixPaginationNavOnVideoSearchPage()
  191. {
  192. $('.pagination3').insertAfter($('div.nf-videos .search-video-thumbs'))
  193. }
  194.  
  195. _preparePlaylistPage()
  196. {
  197. document.scrollingElement.addEventListener('scroll', () => {
  198. console.log(1)
  199. if(!(d&&(MG_Utils.hasClass(n,"stopLazyload")||0<=(e=(e=d).getBoundingClientRect()).top&&e.top<=window.innerHeight||0<=e.bottom&&e.bottom<=window.innerHeight))){var e,t,a,i=n.querySelectorAll("li.videoBox"),l=i&&i.length;l&&l<itemsCount&&n&&!MG_Utils.hasClass(n,"js-reachedEnd")&&(l=window.pageYOffset,l=(window.innerHeight||document.documentElement.clientHeight)+l,i=i[i.length-1])&&i.offsetTop<l&&n&&!MG_Utils.hasClass(n,"loading")&&(t=document.createElement("div"),i=document.createElement("img"),a=document.querySelector("#js-playlistTabsNav #editPlaylist"),MG_Utils.addClass(n,"loading"),t.id="loadingDiv",i.src=loadingImg&&loadingImg,t.appendChild(i),n.parentNode&&n.parentNode.appendChild(t),playlistsUtils)&&playlistsUtils.lazyLoadVideos(lazyloadUrl,r,function(e){t&&t.parentNode&&t.parentNode.removeChild(t),e?n.insertAdjacentHTML("beforeend",e):MG_Utils.addClass(n,"js-reachedEnd"),setTimeout(function(){MG_Utils.removeClass(n,"loading")},100),n=document.getElementById("videoPlaylist"),E&&MG_Utils.hasClass(E,"active")&&s(!0),r++,addEditMode(),a&&MG_Utils.hasClass(a,"active")&&MG_Utils.triggerEvent(a,"click")});var o=document.querySelectorAll("#videoPlaylist li.videoBox.notLoaded");setTimeout(function(){o&&[].forEach.call(o,function(e){var t,a;isThumbVisible(e)&&(t=e.querySelector("img.thumb"))&&void 0!==(a=t.getAttribute("data-thumb_url"))&&(t.setAttribute("src",a),MG_Utils.removeClass(e,"notLoaded"))})},100)}
  200.  
  201. })
  202. }
  203.  
  204. /**
  205. * Removes any IFrames being displayed by going over the page repeatedly till none exist
  206. * @private
  207. */
  208. _removeIframes()
  209. {
  210. let removeMilkTruckIframes = () => {
  211. let iframes = $('milktruck')
  212. let count = iframes.length
  213. iframes.remove()
  214. return count
  215. }
  216.  
  217. this._performFlaggedOperation(UI_REMOVE_IFRAMES, () => {
  218. Validator.iFramesRemover()
  219. let iframesCount
  220. do {
  221. iframesCount = removeMilkTruckIframes()
  222. } while (iframesCount)
  223. })
  224. }
  225.  
  226. _removeLoadMoreButtons()
  227. {
  228. $('.more_recommended_btn, #loadMoreRelatedVideosCenter').remove()
  229. }
  230.  
  231. /**
  232. * @private
  233. */
  234. _removePremiumSectionFromSearchPage()
  235. {
  236. $('.nf-videos .sectionWrapper .sectionTitle h2').each((index, element) => {
  237. let sectionTitle = $(element)
  238. if (sectionTitle.text().trim() === 'Premium Videos') {
  239. sectionTitle.parents('.sectionWrapper:first').remove()
  240. return false
  241. }
  242. })
  243. }
  244.  
  245. /**
  246. * Removes premium video sections from profiles
  247. * @private
  248. */
  249. _removeVideoSectionsOnProfilePage()
  250. {
  251. const videoSections = [
  252. {setting: this._getConfig(FILTER_PAID_VIDEOS), linkSuffix: 'paid'},
  253. {setting: this._getConfig(FILTER_PREMIUM_VIDEOS), linkSuffix: 'fanonly'},
  254. {setting: this._getConfig(FILTER_PRIVATE_VIDEOS), linkSuffix: 'private'},
  255. ]
  256. for (let videoSection of videoSections) {
  257. let videoSectionWrapper = $('.videoSection > div > div > h2 > a[href$="/' + videoSection.linkSuffix + '"]').parents('.videoSection:first')
  258. videoSection.setting ? videoSectionWrapper.show() : videoSectionWrapper.hide()
  259. }
  260. }
  261.  
  262. /**
  263. * @private
  264. */
  265. _setupCompliance()
  266. {
  267. this._onFirstHitAfterCompliance = (item) => {
  268. if (IS_PLAYLIST_PAGE) {
  269. this._validatePlaylistVideoLink(item)
  270. }
  271. }
  272.  
  273. this._playlistPageUsername = ''
  274. this._profilePageUsername = ''
  275.  
  276. if (IS_FEED_PAGE) {
  277. this._onAfterInitialization = () => ChildObserver.create().onNodesAdded((itemsAdded) => {
  278. let itemsList
  279. for (let item of itemsAdded) {
  280. if (typeof item.querySelector === 'function') {
  281. itemsList = item.querySelector(this._config.itemListSelectors)
  282. if (itemsList) {
  283. this._complyItemsList($(itemsList))
  284. }
  285. }
  286. }
  287. }).observe($('#moreData')[0])
  288. } else if (IS_VIDEO_SEARCH_PAGE) {
  289. this._onAfterInitialization = () => this._performFlaggedOperation(UI_AUTO_NEXT, () => this._autoNext())
  290. }
  291. }
  292.  
  293. /**
  294. * @private
  295. */
  296. _setupComplianceFilters()
  297. {
  298. this._addItemTextSanitizationFilter(
  299. 'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. Requires page reload to apply. Example Rule: boyfriend=stepson,stepdad')
  300. this._addItemWhitelistFilter('Show videos with specified phrases in their names. Separate the phrases with line breaks.')
  301. this._addItemTextSearchFilter()
  302. this._addItemComplianceFilter(FILTER_WATCHED_VIDEOS, (item, value) => {
  303. if (value === '1') {
  304. return !this._get(item, FILTER_WATCHED_VIDEOS)
  305. } else if (value === '2') {
  306. return this._get(item, FILTER_WATCHED_VIDEOS)
  307. }
  308. return true
  309. })
  310. this._addItemPercentageRatingRangeFilter('.value')
  311. this._addItemDurationRangeFilter('.duration')
  312. this._addItemComplianceFilter(FILTER_VIDEOS_VIEWS)
  313. this._addItemComplianceFilter(FILTER_PRO_CHANNEL_VIDEOS)
  314. this._addItemComplianceFilter(FILTER_PAID_VIDEOS)
  315. this._addItemComplianceFilter(FILTER_PREMIUM_VIDEOS)
  316. this._addItemComplianceFilter(FILTER_PRIVATE_VIDEOS)
  317. this._addItemComplianceFilter(FILTER_RECOMMENDED_VIDEOS)
  318. this._addItemComplianceFilter(FILTER_USER, (item, users) => !users.includes(this._get(item, FILTER_USER)))
  319. this._addSubscriptionsFilter(() => !IS_FEED_PAGE, (item) => {
  320. let username = this._get(item, FILTER_USER)
  321. return (username === this._playlistPageUsername || username === this._profilePageUsername) ? false : username
  322. })
  323. this._addItemBlacklistFilter('Hide videos with specified phrases in their names.')
  324. }
  325.  
  326. /**
  327. * @private
  328. */
  329. _setupUI()
  330. {
  331. this._onBeforeUIBuild = () => {
  332. this._removeIframes()
  333.  
  334. if (IS_VIDEO_PAGE) {
  335. this._performFlaggedOperation(FILTER_PAID_VIDEOS, () => $('#p2vVideosVPage').remove())
  336. this._performFlaggedOperation(UI_LARGE_PLAYER_ALWAYS, () => this._enlargePlayer())
  337. this._removeLoadMoreButtons()
  338. Validator.sanitizeNodeOfSelector('.inlineFree', this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized)
  339. } else {
  340. if (IS_VIDEO_SEARCH_PAGE) {
  341. this._performFlaggedOperation(UI_REMOVE_PORN_STAR_SECTIONS, () => $('#relatedPornstarSidebar').remove())
  342. this._performFlaggedOperation(FILTER_PREMIUM_VIDEOS, () => this._removePremiumSectionFromSearchPage())
  343. this._fixLeftOverSpaceOnVideoSearchPage()
  344. this._fixPaginationNavOnVideoSearchPage()
  345. } else {
  346. if (IS_PROFILE_PAGE) {
  347. this._removeVideoSectionsOnProfilePage()
  348. this._profilePageUsername = PAGE_PATH_NAME.split('/')[1]
  349. } else {
  350. if (IS_PLAYLIST_PAGE) {
  351. this._preparePlaylistPage()
  352. this._playlistPageUsername = $('#js-aboutPlaylistTabView .usernameWrap a').text().trim()
  353. }
  354. }
  355. }
  356. }
  357. this._performFlaggedOperation(
  358. UI_REMOVE_LIVE_MODELS_SECTIONS,
  359. () => $('.streamateContent').each((index, element) => {$(element).parents('.sectionWrapper:first').remove()})
  360. )
  361. }
  362.  
  363. this._onUIBuild = () =>
  364. this._uiGen.createSettingsSection().append([
  365. this._uiGen.createTabsSection(['Filters', 'Text', 'UI', 'Global', 'Stats'], [
  366. this._uiGen.createTabPanel('Filters', true).append([
  367. this._configurationManager.createElement(FILTER_DURATION_RANGE),
  368. this._configurationManager.createElement(FILTER_PERCENTAGE_RATING_RANGE),
  369. this._configurationManager.createElement(FILTER_VIDEOS_VIEWS),
  370. this._uiGen.createBreakSeparator(),
  371. this._configurationManager.createElement(FILTER_PAID_VIDEOS),
  372. this._configurationManager.createElement(FILTER_PREMIUM_VIDEOS),
  373. this._configurationManager.createElement(FILTER_PRIVATE_VIDEOS),
  374. this._configurationManager.createElement(FILTER_PRO_CHANNEL_VIDEOS),
  375. this._configurationManager.createElement(FILTER_RECOMMENDED_VIDEOS),
  376. this._configurationManager.createElement(FILTER_SUBSCRIBED_VIDEOS),
  377. this._configurationManager.createElement(FILTER_UNRATED),
  378. this._uiGen.createSeparator(),
  379. this._configurationManager.createElement(FILTER_WATCHED_VIDEOS),
  380. this._uiGen.createSeparator(),
  381. this._configurationManager.createElement(FILTER_USER),
  382. this._uiGen.createSeparator(),
  383. this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
  384. ]),
  385. this._uiGen.createTabPanel('Text').append([
  386. this._configurationManager.createElement(FILTER_TEXT_SEARCH),
  387. this._configurationManager.createElement(FILTER_TEXT_BLACKLIST),
  388. this._configurationManager.createElement(FILTER_TEXT_WHITELIST),
  389. this._configurationManager.createElement(FILTER_TEXT_SANITIZATION),
  390. ]),
  391. this._uiGen.createTabPanel('UI').append([
  392. this._configurationManager.createElement(UI_LARGE_PLAYER_ALWAYS),
  393. this._configurationManager.createElement(LINK_DISABLE_PLAYLIST_CONTROLS),
  394. this._configurationManager.createElement(LINK_USER_PUBLIC_VIDEOS),
  395. this._configurationManager.createElement(UI_AUTO_NEXT),
  396. this._uiGen.createSeparator(),
  397. this._configurationManager.createElement(UI_REMOVE_IFRAMES),
  398. this._configurationManager.createElement(UI_REMOVE_LIVE_MODELS_SECTIONS),
  399. this._configurationManager.createElement(UI_REMOVE_PORN_STAR_SECTIONS),
  400. this._uiGen.createSeparator(),
  401. this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
  402. ]),
  403. this._uiGen.createTabPanel('Global').append([
  404. this._uiGen.createFormSection('Account').append([
  405. this._createSubscriptionLoaderControls(),
  406. ]),
  407. this._uiGen.createSeparator(),
  408. this._createSettingsBackupRestoreFormActions(),
  409. ]),
  410. this._uiGen.createTabPanel('Stats').append([
  411. this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
  412. this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
  413. this._uiGen.createStatisticsFormGroup(FILTER_DURATION_RANGE),
  414. this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH),
  415. this._uiGen.createStatisticsFormGroup(FILTER_PAID_VIDEOS, 'Paid Videos'),
  416. this._uiGen.createStatisticsFormGroup(FILTER_PREMIUM_VIDEOS, 'Premium Videos'),
  417. this._uiGen.createStatisticsFormGroup(FILTER_PRIVATE_VIDEOS, 'Private Videos'),
  418. this._uiGen.createStatisticsFormGroup(FILTER_PRO_CHANNEL_VIDEOS, 'Pro Channel Videos'),
  419. this._uiGen.createStatisticsFormGroup(FILTER_PERCENTAGE_RATING_RANGE),
  420. this._uiGen.createStatisticsFormGroup(FILTER_RECOMMENDED_VIDEOS, 'Recommended'),
  421. this._uiGen.createStatisticsFormGroup(FILTER_SUBSCRIBED_VIDEOS, 'Subscribed'),
  422. this._uiGen.createStatisticsFormGroup(FILTER_UNRATED, 'Unrated'),
  423. this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS),
  424. this._uiGen.createStatisticsFormGroup(FILTER_WATCHED_VIDEOS, 'Watched'),
  425. this._uiGen.createSeparator(),
  426. this._uiGen.createStatisticsTotalsGroup(),
  427. ]),
  428. ]),
  429. this._createSettingsFormActions(),
  430. this._uiGen.createSeparator(),
  431. this._uiGen.createStatusSection(),
  432. ])
  433.  
  434. this._onAfterUIBuild = () => {
  435. this._performFlaggedOperation(LINK_USER_PUBLIC_VIDEOS, () => this._complyProfileLinks())
  436. this._uiGen.getSelectedSection()[0].userScript = this
  437. }
  438. }
  439.  
  440. /**
  441. * Validate and change playlist video links
  442. * @param {JQuery} videoItem
  443. * @private
  444. */
  445. _validatePlaylistVideoLink(videoItem)
  446. {
  447. if (this._getConfig(LINK_DISABLE_PLAYLIST_CONTROLS)) {
  448. videoItem.find('a.linkVideoThumb, span.title a').each((index, playlistLink) => {
  449. playlistLink = $(playlistLink)
  450. playlistLink.attr('href', playlistLink.attr('href').replace(/&pkey.*/, ''))
  451. })
  452. }
  453. }
  454. }
  455.  
  456. (new PHSearchAndUITweaks).init()