- // ==UserScript==
- // @name Hitomi - Search & UI Tweaks
- // @namespace brazenvoid
- // @version 4.0.1
- // @author brazenvoid
- // @license GPL-3.0-only
- // @description Various search filters and user experience enhancers
- // @match https://hitomi.la/*
- // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js
- // @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=889107
- // @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=892799
- // @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=889085
- // @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=890597
- // @grant GM_addStyle
- // @run-at document-end
- // ==/UserScript==
-
- GM_addStyle(
- `#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}
- #settings-wrapper button{font-family:Open Sans;font-size:12px;font-weight:700}
- #settings-wrapper textarea.form-input{height:25vh;line-height:2;overflow:auto;padding:0 10px;resize:vertical;white-space:pre;width:92%}
- .bg-brand{background-color:rgb(216, 210, 234)}`)
-
- const PAGE_PATH_NAME = window.location.pathname
-
- const IS_GALLERY_PAGE = $('#dl-button').length
-
- const FILTER_GALLERY_TYPES = 'Show Gallery Types'
- const FILTER_TAG_BLACKLIST = 'Tag Blacklist'
- const FILTER_LANGUAGES = 'Languages'
-
- const OPTION_REMOVE_RELATED_GALLERIES = 'Remove Related Galleries'
-
- const SCRIPT_PREFIX = 'hitomi-sui-'
-
- const SELECTOR_ITEM = '.acg,.anime,.cg,.dj,.manga'
-
- class HitomiSearchAndUITweaks extends BrazenBaseSearchEnhancer
- {
- constructor ()
- {
- super(SCRIPT_PREFIX, SELECTOR_ITEM)
-
- this._configurationManager.
- addCheckboxesGroup(FILTER_GALLERY_TYPES, [
- ['Anime', 'anime'],
- ['Artist CG', 'acg'],
- ['Doujinshi', 'dj'],
- ['Game CG', 'cg'],
- ['Manga', 'manga'],
- ], 'Show only selected gallery types.').
- addCheckboxesGroup(FILTER_LANGUAGES, [
- ['N/A', 'not-applicable'],
- ['Japanese', 'japanese'],
- ['Chinese', 'chinese'],
- ['English', 'english'],
- ['Albanian', 'albanian'],
- ['Arabic', 'arabic'],
- ['Bulgarian', 'bulgarian'],
- ['Catalan', 'catalan'],
- ['Cebuano', 'cebuano'],
- ['Czech', 'czech'],
- ['Danish', 'danish'],
- ['Dutch', 'dutch'],
- ['Esperanto', 'esperanto'],
- ['Estonian', 'estonian'],
- ['Finnish', 'finnish'],
- ['French', 'french'],
- ['German', 'german'],
- ['Greek', 'greek'],
- ['Hebrew', 'hebrew'],
- ['Hungarian', 'hungarian'],
- ['Indonesian', 'indonesian'],
- ['Italian', 'italian'],
- ['Korean', 'korean'],
- ['Latin', 'latin'],
- ['Mongolian', 'mongolian'],
- ['Norwegian', 'norwegian'],
- ['Persian', 'persian'],
- ['Polish', 'polish'],
- ['Portuguese', 'portuguese'],
- ['Romanian', 'romanian'],
- ['Russian', 'russian'],
- ['Slovak', 'slovak'],
- ['Spanish', 'spanish'],
- ['Swedish', 'swedish'],
- ['Tagalog', 'tagalog'],
- ['Thai', 'thai'],
- ['Turkish', 'turkish'],
- ['Ukrainian', 'ukrainian'],
- ['Unspecified', 'unspecified'],
- ['Vietnamese', 'vietnamese'],
- ], 'Select languages to show').
- addFlagField(OPTION_REMOVE_RELATED_GALLERIES, 'Remove related galleries section from gallery pages.').
- addRulesetField(
- FILTER_TAG_BLACKLIST,
- 10,
- '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.',
- null,
- null,
- (blacklistedTags) => this._optimizeBlacklistRules(blacklistedTags))
-
- this._setupUI()
- this._setupCompliance()
- this._setupComplianceFilters()
- }
-
- /**
- * @param {string} tag
- * @return {JQuery.Selector}
- * @private
- */
- _formatTagSelector (tag)
- {
- return 'a[href$="' + encodeURIComponent(tag.trim()) + '-all.html"]'
- }
-
- /**
- * @param {string[][]} ruleset
- * @param {string[]} tags
- * @return {string[][]}
- * @private
- */
- _growBlacklistRuleset (ruleset, tags)
- {
- let grownRuleset = []
- for (let tag of tags) {
- for (let rule of ruleset) {
- grownRuleset.push([...rule, this._formatTagSelector(tag)])
- }
- }
- return grownRuleset
- }
-
- /**
- * @private
- */
- _optimizeBlacklistRules (blacklistedRules)
- {
- let orTags, iteratedRuleset
- let optimizedRuleset = []
-
- // Translate user defined rules
-
- for (let blacklistedRule of blacklistedRules) {
-
- iteratedRuleset = []
- for (let andTag of blacklistedRule.split('&')) {
-
- orTags = andTag.split('|')
- if (orTags.length === 1) {
- this._updateBlacklistRuleset(iteratedRuleset, andTag)
- } else {
- if (iteratedRuleset.length) {
- iteratedRuleset = this._growBlacklistRuleset(iteratedRuleset, orTags)
- } else {
- this._updateBlacklistRuleset(iteratedRuleset, orTags)
- }
- }
- }
- optimizedRuleset = optimizedRuleset.concat(iteratedRuleset)
- }
-
- // Sort rules by complexity
-
- return optimizedRuleset.sort((a, b) => a.length - b.length)
- }
-
- /**
- * @private
- */
- _removeRelatedGalleries ()
- {
- if (IS_GALLERY_PAGE && this._configurationManager.getValue(OPTION_REMOVE_RELATED_GALLERIES)) {
- $('.gallery-content').remove()
- }
- }
-
- /**
- * @private
- */
- _setupCompliance ()
- {
- this._onGetItemLists = () => $('.gallery-content')
-
- this._onGetItemName = (item) => item.find('h1.lillie a').text()
- }
-
- /**
- * @private
- */
- _setupComplianceFilters ()
- {
- this._addItemComplianceFilter(FILTER_LANGUAGES, (valueKeys) => valueKeys.length, (item, valueKeys) => {
- let languageLink = item.find('tr:nth-child(3) > td:nth-child(2) a')
- if (languageLink.length) {
-
- languageLink = languageLink.attr('href')
- for (let key of valueKeys) {
-
- if (languageLink.includes(key)) {
- return true
- }
- }
- return false
- }
- return valueKeys.includes('not-applicable')
- })
- this._addItemComplianceFilter(FILTER_GALLERY_TYPES, (valueKeys) => valueKeys.length, (item, valueKeys) => {
- for (let galleryClass of valueKeys) {
- if (item.hasClass(galleryClass)) {
- return true
- }
- }
- return false
- })
- this._addItemComplianceFilter(
- FILTER_TAG_BLACKLIST,
- (blacklistedTags) => blacklistedTags.length,
- (item, blacklistRuleset) => {
- let isBlacklisted
- let itemTagsSection = item.find('.relatedtags')
-
- for (let rule of blacklistRuleset) {
-
- isBlacklisted = true
- for (let tagSelector of rule) {
- if (itemTagsSection.find(tagSelector).length !== 1) {
- isBlacklisted = false
- break
- }
- }
- if (isBlacklisted) {
- return false
- }
- }
- return true
- }
- )
- }
-
- /**
- * @private
- */
- _setupUI ()
- {
- this._onBeforeUIBuild = () => {
- if (IS_GALLERY_PAGE) {
- this._removeRelatedGalleries()
- }
- }
-
- this._onUIBuild = () =>
- this._uiGen.createSettingsSection().append([
- this._uiGen.createTabsSection(['Filters', 'Languages', 'Global', 'Stats'], [
- this._uiGen.createTabPanel('Filters', true).append([
- this._configurationManager.createElement(FILTER_GALLERY_TYPES),
- this._uiGen.createSeparator(),
- this._configurationManager.createElement(FILTER_TAG_BLACKLIST),
- this._uiGen.createSeparator(),
- this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
- ]),
- this._uiGen.createTabPanel('Languages').append([
- this._configurationManager.createElement(FILTER_LANGUAGES),
- ]),
- this._uiGen.createTabPanel('Global').append([
- this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
- this._uiGen.createSeparator(),
- this._createSettingsBackupRestoreFormActions(),
- ]),
- this._uiGen.createTabPanel('Stats').append([
- this._uiGen.createStatisticsFormGroup(FILTER_GALLERY_TYPES),
- this._uiGen.createStatisticsFormGroup(FILTER_LANGUAGES),
- this._uiGen.createStatisticsFormGroup(FILTER_TAG_BLACKLIST),
- this._uiGen.createSeparator(),
- this._uiGen.createStatisticsTotalsGroup(),
- ]),
- ]),
- this._createSettingsFormActions(),
- this._uiGen.createSeparator(),
- this._uiGen.createStatusSection(),
- ])
-
- this._onAfterUIBuild = () => {
- this._uiGen.getSelectedSection()[0].userScript = this
- }
- }
-
- /**
- * @param {string[][]} ruleset
- * @param {string|string[]} tagToAdd
- * @private
- */
- _updateBlacklistRuleset (ruleset, tagToAdd)
- {
- if (ruleset.length) {
- for (let rule of ruleset) {
- rule.push(this._formatTagSelector(tagToAdd))
- }
- } else {
- let tags = typeof tagToAdd === 'string' ? [tagToAdd] : tagToAdd
- for (let tag of tags) {
- ruleset.push([this._formatTagSelector(tag)])
- }
- }
- }
- }
-
- (new HitomiSearchAndUITweaks).init()