Sleazy Fork is available in English.

Hitomi - Search & UI Tweaks

Various search filters and user experience enhancers

Versão de: 19/01/2021. Veja: a última versão.

// ==UserScript==
// @name         Hitomi - Search & UI Tweaks
// @namespace    brazenvoid
// @version      4.0.0
// @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=889083
// @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,
                '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()