Sleazy Fork is available in English.

Hitomi - Search & UI Tweaks

Various search filters and user experience enhancers

目前为 2021-01-20 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Hitomi - Search & UI Tweaks
  3. // @namespace brazenvoid
  4. // @version 4.0.1
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Various search filters and user experience enhancers
  8. // @match https://hitomi.la/*
  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=889107
  11. // @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=892799
  12. // @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=889085
  13. // @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=890597
  14. // @grant GM_addStyle
  15. // @run-at document-end
  16. // ==/UserScript==
  17.  
  18. GM_addStyle(
  19. `#settings-wrapper{font-family:Open Sans;font-size:12px;font-weight:700;line-height:1;background-color:rgb(216, 210, 234);top:5vh;width:300px}
  20. #settings-wrapper button{font-family:Open Sans;font-size:12px;font-weight:700}
  21. #settings-wrapper textarea.form-input{height:25vh;line-height:2;overflow:auto;padding:0 10px;resize:vertical;white-space:pre;width:92%}
  22. .bg-brand{background-color:rgb(216, 210, 234)}`)
  23.  
  24. const PAGE_PATH_NAME = window.location.pathname
  25.  
  26. const IS_GALLERY_PAGE = $('#dl-button').length
  27.  
  28. const FILTER_GALLERY_TYPES = 'Show Gallery Types'
  29. const FILTER_TAG_BLACKLIST = 'Tag Blacklist'
  30. const FILTER_LANGUAGES = 'Languages'
  31.  
  32. const OPTION_REMOVE_RELATED_GALLERIES = 'Remove Related Galleries'
  33.  
  34. const SCRIPT_PREFIX = 'hitomi-sui-'
  35.  
  36. const SELECTOR_ITEM = '.acg,.anime,.cg,.dj,.manga'
  37.  
  38. class HitomiSearchAndUITweaks extends BrazenBaseSearchEnhancer
  39. {
  40. constructor ()
  41. {
  42. super(SCRIPT_PREFIX, SELECTOR_ITEM)
  43.  
  44. this._configurationManager.
  45. addCheckboxesGroup(FILTER_GALLERY_TYPES, [
  46. ['Anime', 'anime'],
  47. ['Artist CG', 'acg'],
  48. ['Doujinshi', 'dj'],
  49. ['Game CG', 'cg'],
  50. ['Manga', 'manga'],
  51. ], 'Show only selected gallery types.').
  52. addCheckboxesGroup(FILTER_LANGUAGES, [
  53. ['N/A', 'not-applicable'],
  54. ['Japanese', 'japanese'],
  55. ['Chinese', 'chinese'],
  56. ['English', 'english'],
  57. ['Albanian', 'albanian'],
  58. ['Arabic', 'arabic'],
  59. ['Bulgarian', 'bulgarian'],
  60. ['Catalan', 'catalan'],
  61. ['Cebuano', 'cebuano'],
  62. ['Czech', 'czech'],
  63. ['Danish', 'danish'],
  64. ['Dutch', 'dutch'],
  65. ['Esperanto', 'esperanto'],
  66. ['Estonian', 'estonian'],
  67. ['Finnish', 'finnish'],
  68. ['French', 'french'],
  69. ['German', 'german'],
  70. ['Greek', 'greek'],
  71. ['Hebrew', 'hebrew'],
  72. ['Hungarian', 'hungarian'],
  73. ['Indonesian', 'indonesian'],
  74. ['Italian', 'italian'],
  75. ['Korean', 'korean'],
  76. ['Latin', 'latin'],
  77. ['Mongolian', 'mongolian'],
  78. ['Norwegian', 'norwegian'],
  79. ['Persian', 'persian'],
  80. ['Polish', 'polish'],
  81. ['Portuguese', 'portuguese'],
  82. ['Romanian', 'romanian'],
  83. ['Russian', 'russian'],
  84. ['Slovak', 'slovak'],
  85. ['Spanish', 'spanish'],
  86. ['Swedish', 'swedish'],
  87. ['Tagalog', 'tagalog'],
  88. ['Thai', 'thai'],
  89. ['Turkish', 'turkish'],
  90. ['Ukrainian', 'ukrainian'],
  91. ['Unspecified', 'unspecified'],
  92. ['Vietnamese', 'vietnamese'],
  93. ], 'Select languages to show').
  94. addFlagField(OPTION_REMOVE_RELATED_GALLERIES, 'Remove related galleries section from gallery pages.').
  95. addRulesetField(
  96. FILTER_TAG_BLACKLIST,
  97. 10,
  98. 'Specify the tags blacklist with one rule on each line. While single tags can be specified, complex multi-tag rules with & (AND) and | (OR) can also be defined.',
  99. null,
  100. null,
  101. (blacklistedTags) => this._optimizeBlacklistRules(blacklistedTags))
  102.  
  103. this._setupUI()
  104. this._setupCompliance()
  105. this._setupComplianceFilters()
  106. }
  107.  
  108. /**
  109. * @param {string} tag
  110. * @return {JQuery.Selector}
  111. * @private
  112. */
  113. _formatTagSelector (tag)
  114. {
  115. return 'a[href$="' + encodeURIComponent(tag.trim()) + '-all.html"]'
  116. }
  117.  
  118. /**
  119. * @param {string[][]} ruleset
  120. * @param {string[]} tags
  121. * @return {string[][]}
  122. * @private
  123. */
  124. _growBlacklistRuleset (ruleset, tags)
  125. {
  126. let grownRuleset = []
  127. for (let tag of tags) {
  128. for (let rule of ruleset) {
  129. grownRuleset.push([...rule, this._formatTagSelector(tag)])
  130. }
  131. }
  132. return grownRuleset
  133. }
  134.  
  135. /**
  136. * @private
  137. */
  138. _optimizeBlacklistRules (blacklistedRules)
  139. {
  140. let orTags, iteratedRuleset
  141. let optimizedRuleset = []
  142.  
  143. // Translate user defined rules
  144.  
  145. for (let blacklistedRule of blacklistedRules) {
  146.  
  147. iteratedRuleset = []
  148. for (let andTag of blacklistedRule.split('&')) {
  149.  
  150. orTags = andTag.split('|')
  151. if (orTags.length === 1) {
  152. this._updateBlacklistRuleset(iteratedRuleset, andTag)
  153. } else {
  154. if (iteratedRuleset.length) {
  155. iteratedRuleset = this._growBlacklistRuleset(iteratedRuleset, orTags)
  156. } else {
  157. this._updateBlacklistRuleset(iteratedRuleset, orTags)
  158. }
  159. }
  160. }
  161. optimizedRuleset = optimizedRuleset.concat(iteratedRuleset)
  162. }
  163.  
  164. // Sort rules by complexity
  165.  
  166. return optimizedRuleset.sort((a, b) => a.length - b.length)
  167. }
  168.  
  169. /**
  170. * @private
  171. */
  172. _removeRelatedGalleries ()
  173. {
  174. if (IS_GALLERY_PAGE && this._configurationManager.getValue(OPTION_REMOVE_RELATED_GALLERIES)) {
  175. $('.gallery-content').remove()
  176. }
  177. }
  178.  
  179. /**
  180. * @private
  181. */
  182. _setupCompliance ()
  183. {
  184. this._onGetItemLists = () => $('.gallery-content')
  185.  
  186. this._onGetItemName = (item) => item.find('h1.lillie a').text()
  187. }
  188.  
  189. /**
  190. * @private
  191. */
  192. _setupComplianceFilters ()
  193. {
  194. this._addItemComplianceFilter(FILTER_LANGUAGES, (valueKeys) => valueKeys.length, (item, valueKeys) => {
  195. let languageLink = item.find('tr:nth-child(3) > td:nth-child(2) a')
  196. if (languageLink.length) {
  197.  
  198. languageLink = languageLink.attr('href')
  199. for (let key of valueKeys) {
  200.  
  201. if (languageLink.includes(key)) {
  202. return true
  203. }
  204. }
  205. return false
  206. }
  207. return valueKeys.includes('not-applicable')
  208. })
  209. this._addItemComplianceFilter(FILTER_GALLERY_TYPES, (valueKeys) => valueKeys.length, (item, valueKeys) => {
  210. for (let galleryClass of valueKeys) {
  211. if (item.hasClass(galleryClass)) {
  212. return true
  213. }
  214. }
  215. return false
  216. })
  217. this._addItemComplianceFilter(
  218. FILTER_TAG_BLACKLIST,
  219. (blacklistedTags) => blacklistedTags.length,
  220. (item, blacklistRuleset) => {
  221. let isBlacklisted
  222. let itemTagsSection = item.find('.relatedtags')
  223.  
  224. for (let rule of blacklistRuleset) {
  225.  
  226. isBlacklisted = true
  227. for (let tagSelector of rule) {
  228. if (itemTagsSection.find(tagSelector).length !== 1) {
  229. isBlacklisted = false
  230. break
  231. }
  232. }
  233. if (isBlacklisted) {
  234. return false
  235. }
  236. }
  237. return true
  238. }
  239. )
  240. }
  241.  
  242. /**
  243. * @private
  244. */
  245. _setupUI ()
  246. {
  247. this._onBeforeUIBuild = () => {
  248. if (IS_GALLERY_PAGE) {
  249. this._removeRelatedGalleries()
  250. }
  251. }
  252.  
  253. this._onUIBuild = () =>
  254. this._uiGen.createSettingsSection().append([
  255. this._uiGen.createTabsSection(['Filters', 'Languages', 'Global', 'Stats'], [
  256. this._uiGen.createTabPanel('Filters', true).append([
  257. this._configurationManager.createElement(FILTER_GALLERY_TYPES),
  258. this._uiGen.createSeparator(),
  259. this._configurationManager.createElement(FILTER_TAG_BLACKLIST),
  260. this._uiGen.createSeparator(),
  261. this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
  262. ]),
  263. this._uiGen.createTabPanel('Languages').append([
  264. this._configurationManager.createElement(FILTER_LANGUAGES),
  265. ]),
  266. this._uiGen.createTabPanel('Global').append([
  267. this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
  268. this._uiGen.createSeparator(),
  269. this._createSettingsBackupRestoreFormActions(),
  270. ]),
  271. this._uiGen.createTabPanel('Stats').append([
  272. this._uiGen.createStatisticsFormGroup(FILTER_GALLERY_TYPES),
  273. this._uiGen.createStatisticsFormGroup(FILTER_LANGUAGES),
  274. this._uiGen.createStatisticsFormGroup(FILTER_TAG_BLACKLIST),
  275. this._uiGen.createSeparator(),
  276. this._uiGen.createStatisticsTotalsGroup(),
  277. ]),
  278. ]),
  279. this._createSettingsFormActions(),
  280. this._uiGen.createSeparator(),
  281. this._uiGen.createStatusSection(),
  282. ])
  283.  
  284. this._onAfterUIBuild = () => {
  285. this._uiGen.getSelectedSection()[0].userScript = this
  286. }
  287. }
  288.  
  289. /**
  290. * @param {string[][]} ruleset
  291. * @param {string|string[]} tagToAdd
  292. * @private
  293. */
  294. _updateBlacklistRuleset (ruleset, tagToAdd)
  295. {
  296. if (ruleset.length) {
  297. for (let rule of ruleset) {
  298. rule.push(this._formatTagSelector(tagToAdd))
  299. }
  300. } else {
  301. let tags = typeof tagToAdd === 'string' ? [tagToAdd] : tagToAdd
  302. for (let tag of tags) {
  303. ruleset.push([this._formatTagSelector(tag)])
  304. }
  305. }
  306. }
  307. }
  308.  
  309. (new HitomiSearchAndUITweaks).init()