XNXX - Search Filters

Various search filters

  1. // ==UserScript==
  2. // @name XNXX - Search Filters
  3. // @namespace brazenvoid
  4. // @version 4.1.6
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Various search filters
  8. // @match https://www.xnxx.com/*
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
  10. // @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=882642
  11. // @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=880818
  12. // @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=880816
  13. // @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=882641
  14. // @grant GM_addStyle
  15. // @run-at document-idle
  16. // ==/UserScript==
  17.  
  18. GM_addStyle(
  19. `#settings-wrapper{background-color:#ffa31a;top:20vh;width:270px}#settings-wrapper input::selection{background-color: black;color: white}.form-group{margin-bottom:0}label{margin-bottom:0}input.form-input,select.form-input,textarea.form-input{background-color:white;font-size:inherit}`)
  20.  
  21. const PAGE_PATH_NAME = window.location.pathname
  22.  
  23. const IS_HOME_PAGE = PAGE_PATH_NAME === '/'
  24. const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/video-')
  25.  
  26. const ITEM_CLASSES = 'thumb-block:not(.thumb-cat)'
  27.  
  28. const FILTER_VIDEO_RESOLUTION = 'Minimum Resolution'
  29. const FILTER_VIDEOS_RATING = 'Rating'
  30. const FILTER_VIDEOS_DURATION = 'Duration'
  31. const FILTER_VIDEOS_VIEWS = 'Views'
  32.  
  33. const VIDEO_DURATION_KEY = 'XNSSSFDuration'
  34. const VIDEO_NAME_KEY = 'XNSSSFName'
  35. const VIDEO_RATING_KEY = 'XNSSSFRating'
  36. const VIDEO_RESOLUTION_KEY = 'XNSSSFResolution'
  37. const VIDEO_VIEWS_KEY = 'XNSSSFViews'
  38.  
  39. class XNXXSearchFilters extends BrazenBaseSearchEnhancer
  40. {
  41. constructor ()
  42. {
  43. super('xnxx-sf-', ITEM_CLASSES)
  44.  
  45. this._configurationManager.
  46. addRadiosGroup(FILTER_VIDEO_RESOLUTION, [['Show All', 0], ['SD 360p', 360], ['SD 480p', 480], ['HD 720p', 720], ['HD 1080p', 1080]], 'Filter videos by minimum resolution.').
  47. addRangeField(FILTER_VIDEOS_DURATION, 0, 100000, 'Filter videos by duration.').
  48. addRangeField(FILTER_VIDEOS_RATING, 0, 100, 'Filter videos by ratings.').
  49. addRangeField(FILTER_VIDEOS_VIEWS, 0, 10000000, 'Filter videos by view count.').
  50. addRulesetField(FILTER_TEXT_BLACKLIST, 'Hide videos with specified phrases in their names. Separate the phrases with line breaks.').
  51. addRulesetField(FILTER_TEXT_SANITIZATION,
  52. 'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. Example Rule: boyfriend=stepson,stepdad').
  53. addRulesetField(FILTER_TEXT_WHITELIST, 'Show videos with specified phrases in their names. Separate the phrases with line breaks.')
  54.  
  55. // UI Events
  56.  
  57. this._onBeforeUIBuild = () => {
  58. if (IS_VIDEO_PAGE) {
  59. this._validator.sanitizeNodeOfSelector('.clear-infobar > strong:nth-child(1)', this._configurationManager.getField(FILTER_TEXT_SANITIZATION).optimized)
  60. }
  61. }
  62.  
  63. this._onUIBuild = () =>
  64. this._uiGen.createSettingsSection().append([
  65. this._uiGen.createTabsSection(['Filters', 'Text', 'Global', 'Stats'], [
  66. this._uiGen.createTabPanel('Filters', true).append([
  67. this._configurationManager.createElement(FILTER_VIDEOS_DURATION),
  68. this._configurationManager.createElement(FILTER_VIDEOS_RATING),
  69. this._configurationManager.createElement(FILTER_VIDEOS_VIEWS),
  70. this._uiGen.createSeparator(),
  71. this._configurationManager.createElement(FILTER_VIDEO_RESOLUTION),
  72. this._uiGen.createSeparator(),
  73. this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
  74. ]),
  75. this._uiGen.createTabPanel('Text').append([
  76. this._configurationManager.createElement(FILTER_TEXT_SEARCH),
  77. this._configurationManager.createElement(FILTER_TEXT_BLACKLIST),
  78. this._configurationManager.createElement(FILTER_TEXT_WHITELIST),
  79. ]),
  80. this._uiGen.createTabPanel('Global').append([
  81. this._configurationManager.createElement(FILTER_TEXT_SANITIZATION),
  82. this._uiGen.createSeparator(),
  83. // this._configurationManager.createElement(CONFIG_PAGINATOR_THRESHOLD),
  84. // this._configurationManager.createElement(CONFIG_PAGINATOR_LIMIT),
  85. this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
  86. ]),
  87. this._uiGen.createTabPanel('Stats').append([
  88. this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
  89. this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_DURATION),
  90. this._uiGen.createStatisticsFormGroup(FILTER_VIDEO_RESOLUTION),
  91. this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_RATING),
  92. this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH),
  93. this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS),
  94. this._uiGen.createSeparator(),
  95. this._uiGen.createStatisticsTotalsGroup(),
  96. ]),
  97. ]),
  98. this._createSettingsFormActions(),
  99. this._uiGen.createSeparator(),
  100. this._uiGen.createStatusSection(),
  101. ])
  102.  
  103. this._onAfterUIBuild = () => {
  104. this._uiGen.getSelectedSection()[0].userScript = this
  105. }
  106.  
  107. // Compliance Events
  108.  
  109. this._onGetItemLists = () => $('.mozaique')
  110.  
  111. this._onGetItemName = (videoItem) => videoItem[0][VIDEO_NAME_KEY]
  112.  
  113. this._onFirstHitBeforeCompliance = (item) => this._analyzeVideoItem(item)
  114.  
  115. this._complianceFilters = [
  116. (videoItem) => this._validateSearch(videoItem),
  117. (videoItem) => this._validateRating(videoItem),
  118. (videoItem) => this._validateDuration(videoItem),
  119. (videoItem) => this._validateViews(videoItem),
  120. (videoItem) => this._validateResolution(videoItem),
  121. (videoItem) => this._validateItemBlacklist(videoItem),
  122. ]
  123.  
  124. this._onFirstHitAfterCompliance =
  125. (item) => this._validator.sanitizeTextNode(item.find('.thumb-under a:first'), this._configurationManager.getField(FILTER_TEXT_SANITIZATION).optimized)
  126. }
  127.  
  128. /**
  129. * @param {JQuery} videoItem
  130. * @private
  131. */
  132. _analyzeVideoItem (videoItem)
  133. {
  134. let videoMetadataElements = videoItem.find('.metadata'), metadata
  135. let videoItemElement = videoItem[0]
  136.  
  137. if (videoMetadataElements) {
  138. if (IS_VIDEO_PAGE) {
  139. metadata = videoMetadataElements.text().split(' ')
  140. videoItemElement[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(metadata[1])
  141. videoItemElement[VIDEO_RATING_KEY] = 100
  142. videoItemElement[VIDEO_RESOLUTION_KEY] = parseInt(metadata[5].replace('p', ''))
  143. videoItemElement[VIDEO_VIEWS_KEY] = metadata[0]
  144. } else {
  145. metadata = videoMetadataElements.text().split('\n')
  146. if (metadata.length > 3) {
  147. metadata[1] = metadata[1].split(' ')
  148. videoItemElement[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(metadata[2])
  149. videoItemElement[VIDEO_RATING_KEY] = metadata[1][1].replace('%', '')
  150. videoItemElement[VIDEO_RESOLUTION_KEY] = parseInt(metadata[3].replace(' - ', '').replace('p', ''))
  151. videoItemElement[VIDEO_VIEWS_KEY] = metadata[1][0]
  152. } else {
  153. videoItemElement[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(metadata[1])
  154. videoItemElement[VIDEO_RATING_KEY] = 100
  155. videoItemElement[VIDEO_RESOLUTION_KEY] = parseInt(metadata[2].replace(' - ', '').replace('p', ''))
  156. videoItemElement[VIDEO_VIEWS_KEY] = 0
  157. }
  158. }
  159. videoItemElement[VIDEO_NAME_KEY] = videoItem.find('.thumb-under > p:nth-child(1) > a:nth-child(1)')
  160. }
  161. }
  162.  
  163. /**
  164. * @param {string} durationString
  165. * @return {number}
  166. * @private
  167. */
  168. _analyzeVideItemDuration (durationString)
  169. {
  170. let duration = 0, splitArray
  171.  
  172. if (IS_VIDEO_PAGE) {
  173. splitArray = durationString.split(' ')
  174. for (let i = 0; i < splitArray.length; i++) {
  175. if (splitArray[i].endsWith('min')) {
  176. duration += 60 * splitArray[i].replace('min', '')
  177. } else {
  178. if (splitArray[i].endsWith('sec')) {
  179. duration += splitArray[i].replace('sec', '')
  180. }
  181. }
  182. }
  183. } else {
  184. splitArray = durationString.split('min')
  185. if (splitArray.length === 2) {
  186. duration = 60 * splitArray[0]
  187. } else {
  188. splitArray = durationString.split('sec')
  189. if (splitArray.length === 2) {
  190. duration = splitArray[0]
  191. }
  192. }
  193. }
  194. return duration
  195. }
  196.  
  197. /**
  198. * Validates video duration
  199. * @param {JQuery} videoItem
  200. * @return {boolean}
  201. * @private
  202. */
  203. _validateDuration (videoItem)
  204. {
  205. let range = this._configurationManager.getValue(FILTER_VIDEOS_DURATION)
  206. if (range.minimum > 0 || range.maximum > 0) {
  207. return this._validator.validateRange(FILTER_VIDEOS_DURATION, videoItem[0][VIDEO_DURATION_KEY], [range.minimum, range.maximum])
  208. }
  209. return true
  210. }
  211.  
  212. /**
  213. * Validate video rating
  214. * @param {JQuery} videoItem
  215. * @return {boolean}
  216. * @private
  217. */
  218. _validateRating (videoItem)
  219. {
  220. let range = this._configurationManager.getValue(FILTER_VIDEOS_RATING)
  221. if (range.minimum > 0 || range.maximum > 0) {
  222. return this._validator.validateRange(FILTER_VIDEOS_RATING, videoItem[0][VIDEO_RATING_KEY], [range.minimum, range.maximum])
  223. }
  224. return true
  225. }
  226.  
  227. /**
  228. * Validate video quality
  229. * @param {JQuery} videoItem
  230. * @return {boolean}
  231. * @private
  232. */
  233. _validateResolution (videoItem)
  234. {
  235. let validationCheck = true
  236.  
  237. let resolution = this._configurationManager.getValue(FILTER_VIDEO_RESOLUTION)
  238. if (resolution > 0) {
  239. validationCheck = videoItem[0][VIDEO_RESOLUTION_KEY] >= parseInt(resolution)
  240. this._statistics.record(FILTER_VIDEO_RESOLUTION, validationCheck)
  241. }
  242. return validationCheck
  243. }
  244.  
  245. /**
  246. * Validates existence of searched words in the video name
  247. * @param {JQuery} videoItem
  248. * @return {boolean}
  249. * @private
  250. */
  251. _validateSearch (videoItem)
  252. {
  253. let search = this._configurationManager.getValue(FILTER_TEXT_SEARCH)
  254. return search !== '' ? this._statisticsRecorder.record(FILTER_TEXT_SEARCH, videoItem[0][VIDEO_NAME_KEY].includes(search)) : true
  255. }
  256.  
  257. /**
  258. * Validate video view count
  259. * @param {JQuery} videoItem
  260. * @return {boolean}
  261. * @private
  262. */
  263. _validateViews (videoItem)
  264. {
  265. let range = this._configurationManager.getValue(FILTER_VIDEOS_VIEWS)
  266. if (range.minimum > 0 || range.maximum > 0) {
  267. return this._validator.validateRange(FILTER_VIDEOS_VIEWS, videoItem[0][VIDEO_VIEWS_KEY], [range.minimum, range.maximum])
  268. }
  269. return true
  270. }
  271.  
  272. /**
  273. * Validates non-existence of blacklisted words in the video name
  274. * @param {JQuery} videoItem
  275. * @return {boolean}
  276. * @private
  277. */
  278. _validateWhitelist (videoItem)
  279. {
  280. let regex = this._configurationManager.getField(FILTER_TEXT_WHITELIST).optimized
  281. return regex ? this._validator.validateTextContains(videoItem[0][VIDEO_NAME_KEY], regex, FILTER_TEXT_WHITELIST) : true
  282. }
  283. }
  284.  
  285. (new XNXXSearchFilters).init()