- // ==UserScript==
- // @name Hitomi - Search & UI Tweaks
- // @namespace brazenvoid
- // @version 2.7.0
- // @author brazenvoid
- // @license GPL-3.0-only
- // @description Search Filters & UI Manipulations
- // @include https://hitomi.la/*
- // @require https://greasyfork.org/scripts/375557-brazenvoid-s-base-resource/code/Brazenvoid's%20Base%20Resource.js?version=658367
- // @run-at document-idle
- // ==/UserScript==
-
- // Define languages to keep and tags to exclude here
-
- let settings = {
- allowedGalleryTypes: [
- 'artist cg',
- 'doujinshi',
- 'game cg',
- 'manga',
- ],
- allowedLanguages: [
- 'japanese',
- 'english'
- ],
- excludedTags: [
- 'anthology',
- 'female:daughter',
- 'female:dickgirls only',
- 'female:females only',
- 'female:mother',
- 'sample',
- 'male:father',
- 'male:son',
- 'male:yaoi',
- ],
- excludedTagGroups: [
- ['female:loli', 'female:sole female'],
- ['male:shota', 'male:sole male'],
- ],
- removeRelatedGalleries: true,
- showUIAlways: false,
- debugLogging: false,
- }
-
- // Translating filters into selectors
- // -- Translate gallery types
-
- let allowedGallerySelectors = [], allowedGalleryTypeSelector
-
- for (let allowedGalleryType of settings.allowedGalleryTypes) {
- switch (allowedGalleryType) {
- case 'anime':
- allowedGalleryTypeSelector = 'anime'
- break
- case 'artist cg':
- allowedGalleryTypeSelector = 'acg'
- break
- case 'doujinshi':
- allowedGalleryTypeSelector = 'dj'
- break
- case 'game cg':
- allowedGalleryTypeSelector = 'cg'
- break
- case 'manga':
- allowedGalleryTypeSelector = 'manga'
- break
- default:
- continue
- }
- allowedGallerySelectors.push(allowedGalleryTypeSelector)
- }
-
- // -- Translate tag filters
-
- function encodeURIComponentRFC3986(str) {
- return encodeURIComponent(str).replace(/[-!'()*]/g, function(c) {
- return '%' + c.charCodeAt(0).toString(16).toUpperCase()
- })
- }
-
- let formatFilters = function (filters, prefix, suffix, join) {
-
- let formatFilter = function (filter) {
- return '[href="' + prefix + encodeURIComponentRFC3986(filter) + suffix + '.html"]'
- }
- let index2
- for (let index = 0; index < filters.length; index++) {
- if (Array.isArray(filters[index])) {
- for (index2 = 0; index2 < filters[index].length; index2++) {
- filters[index][index2] = formatFilter(filters[index][index2])
- }
- } else {
- filters[index] = formatFilter(filters[index])
- }
- }
- return join ? filters.join(', ') : filters
- }
-
- let languageFiltersSelector = formatFilters(settings.allowedLanguages, '/index-', '-1', true)
- let tagFiltersSelector = formatFilters(settings.excludedTags, '/tag/', '-all-1', true)
- let tagGroupFiltersSelectors = formatFilters(settings.excludedTagGroups, '/tag/', '-all-1', false)
-
- // Base Resources Initialization
-
- const scriptPrefix = 'hitomi-sui-'
-
- let storage = new LocalStore(scriptPrefix + 'settings', settings)
- settings = storage.retrieve().get()
-
- let logger = new Logger(settings.debugLogging)
- let selectorGenerator = new SelectorGenerator(scriptPrefix)
- let statistics = new StatisticsRecorder(logger, selectorGenerator)
- let uiGenerator = new UIGenerator(settings.showUIAlways, selectorGenerator)
-
- // Local Store Events
-
- let refreshUI = function () {
- let store = this.get()
-
- document.getElementById(selectorGenerator.getSettingsInputSelector('Min Duration')).value = store.duration.minimum
- document.getElementById(selectorGenerator.getSettingsInputSelector('Min Rating')).value = store.rating.minimum
- document.getElementById(selectorGenerator.getSettingsInputSelector('Min Views')).value = store.views.minimum
- }
- storage.onDefaultsLoaded = refreshUI
- storage.onRetrieval = refreshUI
- storage.onUpdated = refreshUI
-
- // Filtration logic
-
- let validateLanguage = function (gallery) {
-
- let validationCheck = true
-
- if (settings.allowedLanguages.length > 0) {
-
- let languageTD = gallery.querySelector('tr:nth-child(3) > td:nth-child(2)')
- if (languageTD.querySelector('a') !== null) {
- validationCheck = languageTD.querySelectorAll(languageFiltersSelector).length > 0
- }
- }
- return validationCheck
- }
-
- let validateTags = function (gallery) {
-
- let validationCheck = true
-
- if (settings.excludedTags.length > 0) {
- validationCheck = gallery.querySelectorAll(tagFiltersSelector).length === 0
- statistics.record('Excluded Tags', validationCheck)
- }
- if (validationCheck && settings.excludedTagGroups.length > 0) {
- for (let tagGroupFilterSelectors of tagGroupFiltersSelectors) {
- validationCheck = gallery.querySelectorAll(tagGroupFilterSelectors.join(', ')).length < tagGroupFilterSelectors.length
- console.log(tagGroupFilterSelectors.join(', ') + ' = ' + validationCheck);
- if (!validationCheck) {
- break
- }
- }
- statistics.record('Excluded Tag Groups', validationCheck)
- }
- return validationCheck
- }
-
- let validateType = function (gallery) {
- let validationCheck = allowedGallerySelectors.includes(gallery.className);
- statistics.record('Excluded Tags', validationCheck)
- return validationCheck
- }
-
- let complianceCallback = function (target) {
-
- let galleries = target.querySelectorAll('.anime, .manga, .dj, .acg, .cg')
- let validationCheck
-
- for (let gallery of galleries) {
-
- validationCheck = validateType(gallery) && validateLanguage(gallery) && validateTags(gallery)
-
- if (!validationCheck) {
- gallery.remove()
- }
- }
- }
-
- // Script Run
-
- let galleriesList = document.querySelector('.gallery-content')
- let isGalleryPage = document.getElementById('dl-button') !== null
-
- if (isGalleryPage && settings.removeRelatedGalleries) {
- galleriesList.remove()
- } else {
- let observer = new ChildObserver(complianceCallback)
- observer.observe(galleriesList, true)
- }