Hitomi - Search & UI Tweaks

Search Filters & UI Manipulations

À partir de 2019-02-09. Voir la dernière version.

  1. // ==UserScript==
  2. // @name Hitomi - Search & UI Tweaks
  3. // @namespace brazenvoid
  4. // @version 2.7.0
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Search Filters & UI Manipulations
  8. // @include https://hitomi.la/*
  9. // @require https://greasyfork.org/scripts/375557-brazenvoid-s-base-resource/code/Brazenvoid's%20Base%20Resource.js?version=658367
  10. // @run-at document-idle
  11. // ==/UserScript==
  12.  
  13. // Define languages to keep and tags to exclude here
  14.  
  15. let settings = {
  16. allowedGalleryTypes: [
  17. 'artist cg',
  18. 'doujinshi',
  19. 'game cg',
  20. 'manga',
  21. ],
  22. allowedLanguages: [
  23. 'japanese',
  24. 'english'
  25. ],
  26. excludedTags: [
  27. 'anthology',
  28. 'female:daughter',
  29. 'female:dickgirls only',
  30. 'female:females only',
  31. 'female:mother',
  32. 'sample',
  33. 'male:father',
  34. 'male:son',
  35. 'male:yaoi',
  36. ],
  37. excludedTagGroups: [
  38. ['female:loli', 'female:sole female'],
  39. ['male:shota', 'male:sole male'],
  40. ],
  41. removeRelatedGalleries: true,
  42. showUIAlways: false,
  43. debugLogging: false,
  44. }
  45.  
  46. // Translating filters into selectors
  47. // -- Translate gallery types
  48.  
  49. let allowedGallerySelectors = [], allowedGalleryTypeSelector
  50.  
  51. for (let allowedGalleryType of settings.allowedGalleryTypes) {
  52. switch (allowedGalleryType) {
  53. case 'anime':
  54. allowedGalleryTypeSelector = 'anime'
  55. break
  56. case 'artist cg':
  57. allowedGalleryTypeSelector = 'acg'
  58. break
  59. case 'doujinshi':
  60. allowedGalleryTypeSelector = 'dj'
  61. break
  62. case 'game cg':
  63. allowedGalleryTypeSelector = 'cg'
  64. break
  65. case 'manga':
  66. allowedGalleryTypeSelector = 'manga'
  67. break
  68. default:
  69. continue
  70. }
  71. allowedGallerySelectors.push(allowedGalleryTypeSelector)
  72. }
  73.  
  74. // -- Translate tag filters
  75.  
  76. function encodeURIComponentRFC3986(str) {
  77. return encodeURIComponent(str).replace(/[-!'()*]/g, function(c) {
  78. return '%' + c.charCodeAt(0).toString(16).toUpperCase()
  79. })
  80. }
  81.  
  82. let formatFilters = function (filters, prefix, suffix, join) {
  83.  
  84. let formatFilter = function (filter) {
  85. return '[href="' + prefix + encodeURIComponentRFC3986(filter) + suffix + '.html"]'
  86. }
  87. let index2
  88. for (let index = 0; index < filters.length; index++) {
  89. if (Array.isArray(filters[index])) {
  90. for (index2 = 0; index2 < filters[index].length; index2++) {
  91. filters[index][index2] = formatFilter(filters[index][index2])
  92. }
  93. } else {
  94. filters[index] = formatFilter(filters[index])
  95. }
  96. }
  97. return join ? filters.join(', ') : filters
  98. }
  99.  
  100. let languageFiltersSelector = formatFilters(settings.allowedLanguages, '/index-', '-1', true)
  101. let tagFiltersSelector = formatFilters(settings.excludedTags, '/tag/', '-all-1', true)
  102. let tagGroupFiltersSelectors = formatFilters(settings.excludedTagGroups, '/tag/', '-all-1', false)
  103.  
  104. // Base Resources Initialization
  105.  
  106. const scriptPrefix = 'hitomi-sui-'
  107.  
  108. let storage = new LocalStore(scriptPrefix + 'settings', settings)
  109. settings = storage.retrieve().get()
  110.  
  111. let logger = new Logger(settings.debugLogging)
  112. let selectorGenerator = new SelectorGenerator(scriptPrefix)
  113. let statistics = new StatisticsRecorder(logger, selectorGenerator)
  114. let uiGenerator = new UIGenerator(settings.showUIAlways, selectorGenerator)
  115.  
  116. // Local Store Events
  117.  
  118. let refreshUI = function () {
  119. let store = this.get()
  120.  
  121. document.getElementById(selectorGenerator.getSettingsInputSelector('Min Duration')).value = store.duration.minimum
  122. document.getElementById(selectorGenerator.getSettingsInputSelector('Min Rating')).value = store.rating.minimum
  123. document.getElementById(selectorGenerator.getSettingsInputSelector('Min Views')).value = store.views.minimum
  124. }
  125. storage.onDefaultsLoaded = refreshUI
  126. storage.onRetrieval = refreshUI
  127. storage.onUpdated = refreshUI
  128.  
  129. // Filtration logic
  130.  
  131. let validateLanguage = function (gallery) {
  132.  
  133. let validationCheck = true
  134.  
  135. if (settings.allowedLanguages.length > 0) {
  136.  
  137. let languageTD = gallery.querySelector('tr:nth-child(3) > td:nth-child(2)')
  138. if (languageTD.querySelector('a') !== null) {
  139. validationCheck = languageTD.querySelectorAll(languageFiltersSelector).length > 0
  140. }
  141. }
  142. return validationCheck
  143. }
  144.  
  145. let validateTags = function (gallery) {
  146.  
  147. let validationCheck = true
  148.  
  149. if (settings.excludedTags.length > 0) {
  150. validationCheck = gallery.querySelectorAll(tagFiltersSelector).length === 0
  151. statistics.record('Excluded Tags', validationCheck)
  152. }
  153. if (validationCheck && settings.excludedTagGroups.length > 0) {
  154. for (let tagGroupFilterSelectors of tagGroupFiltersSelectors) {
  155. validationCheck = gallery.querySelectorAll(tagGroupFilterSelectors.join(', ')).length < tagGroupFilterSelectors.length
  156. console.log(tagGroupFilterSelectors.join(', ') + ' = ' + validationCheck);
  157. if (!validationCheck) {
  158. break
  159. }
  160. }
  161. statistics.record('Excluded Tag Groups', validationCheck)
  162. }
  163. return validationCheck
  164. }
  165.  
  166. let validateType = function (gallery) {
  167. let validationCheck = allowedGallerySelectors.includes(gallery.className);
  168. statistics.record('Excluded Tags', validationCheck)
  169. return validationCheck
  170. }
  171.  
  172. let complianceCallback = function (target) {
  173.  
  174. let galleries = target.querySelectorAll('.anime, .manga, .dj, .acg, .cg')
  175. let validationCheck
  176.  
  177. for (let gallery of galleries) {
  178.  
  179. validationCheck = validateType(gallery) && validateLanguage(gallery) && validateTags(gallery)
  180.  
  181. if (!validationCheck) {
  182. gallery.remove()
  183. }
  184. }
  185. }
  186.  
  187. // Script Run
  188.  
  189. let galleriesList = document.querySelector('.gallery-content')
  190. let isGalleryPage = document.getElementById('dl-button') !== null
  191.  
  192. if (isGalleryPage && settings.removeRelatedGalleries) {
  193. galleriesList.remove()
  194. } else {
  195. let observer = new ChildObserver(complianceCallback)
  196. observer.observe(galleriesList, true)
  197. }