// ==UserScript==
// @name Hitomi - Language & Tag Filtering
// @namespace brazenvoid
// @version 2.6.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Search Filters & UI Manipulations
// @include https://hitomi.la/*
// @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',
'korean',
'english'
],
excludedTags: [
'anthology',
'female:daughter',
'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,
}
// Translate gallery types to css selectors
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)
}
// Formatting filters
let formatFilters = function (filters, prefix, suffix, join) {
let formatFilter = function (filter) {
return '[href="' + prefix + encodeURIComponent(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])
}
filters[index] = filters[index].join(', ')
} 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)
// 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
}
if (validationCheck && settings.excludedTagGroups.length > 0) {
for (let tagGroupFilterSelector of tagGroupFiltersSelectors) {
validationCheck = gallery.querySelectorAll(tagGroupFilterSelector).length < tagGroupFilterSelector.length
if (!validationCheck) {
break
}
}
}
return validationCheck
}
let validateType = function (gallery) {
return allowedGallerySelectors.includes(gallery.className)
}
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 observerConfig = {
attributes: false,
childList: true,
subtree: false
}
complianceCallback(galleriesList)
// Adding observer to check compliance of galleries
let observer = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
complianceCallback(mutation.target)
}
})
observer.observe(galleriesList, observerConfig)
}