// ==UserScript==
// @name E-Hentai - UX Tweaks
// @namespace brazenvoid
// @version 1.3.9
// @author brazenvoid
// @license GPL-3.0-only
// @description Numerous features to enrich your browsing experience
// @match https://e-hentai.org/*
// @match https://exhentai.org/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://update.greasyfork.org/scripts/375557/1244990/Base%20Brazen%20Resource.js
// @require https://update.greasyfork.org/scripts/416104/1384193/Brazen%20UI%20Generator.js
// @require https://update.greasyfork.org/scripts/418665/1245040/Brazen%20Configuration%20Manager.js
// @require https://update.greasyfork.org/scripts/429587/1244644/Brazen%20Item%20Attributes%20Resolver.js
// @require https://update.greasyfork.org/scripts/416105/1384192/Brazen%20Base%20Search%20Enhancer.js
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
GM_addStyle(
`#settings-wrapper{min-width:300px;width:300px}.disliked-tag{background-color:lightcoral !important;color:white !important}.disliked-tag:hover{background-color:indianred !important}.disliked-tag > a{color:white !important}.disliked-tag.favourite-tag{background-color:orange !important}.disliked-tag.favourite-tag:hover{background-color:darkorange !important}.favourite-tag{background-color:mediumseagreen !important;color:white !important}.favourite-tag:hover{background-color:forestgreen !important}.favourite-tag > a{color:white !important}`);
const IS_GALLERY_PAGE = $('#gdt').length;
const IS_SEARCH_PAGE = $('#f_search').length;
const IS_SMALL_WINDOW = $('.stuffbox').length;
const IS_TAG_SEARCH_PAGE = window.location.pathname.startsWith('/tag');
const IS_UPLOADER_SEARCH_PAGE = window.location.pathname.startsWith('/uploader');
const UI_DEFAULTS_PAGE_RANGE = 'Page Range';
const UI_DEFAULTS_PAGE_RANGE_ENABLE = 'Enable Page Range Filter';
const UI_DEFAULTS_RATING = 'Rating';
const UI_DEFAULTS_RATING_ENABLE = 'Enable Rating Filter';
const UI_DEFAULTS_TAGS = 'Tags';
const UI_DEFAULTS_TAGS_ENABLE = 'Enable Default Tags';
const UI_FAVOURITE_TAGS = 'Favourite Tags';
const UI_DISLIKED_TAGS = 'Disliked Tags';
const UI_VISITED_HIGHLIGHT = 'Highlight Visited';
const UI_AUTO_NEXT_ON_OPEN_ALL_IMAGES = 'Auto Next Page';
class EHentaiSearchAndUITweaks extends BrazenBaseSearchEnhancer
{
constructor()
{
super({
isUserLoggedIn: false,
itemDeepAnalysisSelector: '',
itemLinkSelector: 'td.gl3m.glname > a, td.gl3c.glname > a, div.gl2e > div > a, a',
itemListSelectors: 'table.itg, div.itg',
itemNameSelector: 'td.gl3m.glname > a > div.glink, td.gl3c.glname > a > div.glink, div.gl4e.glname > div.glink, div.gl4t.glname.glink',
itemSelectors: 'td.gl2e, div.gl1t',
requestDelay: 0,
scriptPrefix: 'e-hentai-ux-',
tagSelectorGenerator: (tag) => {
tag = tag.trim();
if (IS_GALLERY_PAGE) {
return 'div[id="td_' + tag.replace(' ', '_') + '"]';
}
return 'div.gt[title="' + tag + '"], div.gtl[title="' + tag + '"]';
},
});
this._setupFeatures();
this._setupUI();
this._setupEvents();
}
/**
* @param {string} tag
* @return {string}
* @private
*/
_formatTag(tag)
{
if (tag.includes(':') && !tag.includes('"') && (tag.includes(' ') || tag.includes('+'))) {
tag = tag.replace(':', ':"') + '"';
}
return tag;
}
/**
* @param {{}} range
* @param {URLSearchParams} queryParams
* @private
*/
_handleDefaultPageRangeFilter(range, queryParams)
{
if (range.minimum > 0) {
queryParams.set('f_spf', range.minimum);
}
if (range.maximum > 0) {
queryParams.set('f_spf', range.maximum);
}
}
/**
* @param {string} rating
* @param {URLSearchParams} queryParams
* @private
*/
_handleDefaultRatingsFilter(rating, queryParams)
{
queryParams.set('f_srdd', rating);
}
/**
* @param {string[]} tags
* @param {URLSearchParams} queryParams
* @private
*/
_handleDefaultTags(tags, queryParams)
{
let existingTags = queryParams.get('f_search');
let updatedTags = existingTags;
let include = true;
for (let tag of tags) {
if (!existingTags.includes(tag)) {
updatedTags += '+' + this._formatTag(tag);
} else {
include = false;
break;
}
}
if (include) {
queryParams.set('f_search', updatedTags);
}
}
/**
* @private
*/
_handleDefaults()
{
let queryParams = new URLSearchParams(window.location.search);
let existingParams = queryParams.toString();
if (!queryParams.has('next') &&
(this._getConfig(UI_DEFAULTS_PAGE_RANGE_ENABLE) || this._getConfig(UI_DEFAULTS_RATING_ENABLE) ||
this._getConfig(UI_DEFAULTS_TAGS_ENABLE))) {
if (!queryParams.has('f_search')) {
let existingTag = [];
let urlSegments = window.location.pathname.split('/')
if (IS_TAG_SEARCH_PAGE) {
existingTag = urlSegments.pop().trim();
} else if (IS_UPLOADER_SEARCH_PAGE) {
existingTag = 'uploader:' + urlSegments.pop().trim();
}
queryParams.set('f_search', existingTag.length ? this._formatTag(existingTag) : '');
}
if (!queryParams.has('advsearch')) {
queryParams.set('advsearch', '1');
}
let validatePageRange = (range, defaultValidator) => defaultValidator(range) && !queryParams.has('f_spf') &&
!queryParams.has('f_spt');
this._performTogglableComplexOperation(UI_DEFAULTS_PAGE_RANGE_ENABLE, UI_DEFAULTS_PAGE_RANGE, validatePageRange,
(range) => {
this._handleDefaultPageRangeFilter(range, queryParams);
});
let validateRatingFilter = (range, defaultValidator) => defaultValidator(range) && !queryParams.has('f_srdd');
this._performTogglableComplexOperation(UI_DEFAULTS_RATING_ENABLE, UI_DEFAULTS_RATING, validateRatingFilter,
(rating) => {
this._handleDefaultRatingsFilter(rating, queryParams);
});
this._performTogglableOperation(UI_DEFAULTS_TAGS_ENABLE, UI_DEFAULTS_TAGS, (tags) => {
this._handleDefaultTags(tags, queryParams);
});
let updatedParams = queryParams.toString().replaceAll('%2B', '+');
if (updatedParams !== existingParams) {
if (IS_TAG_SEARCH_PAGE || IS_UPLOADER_SEARCH_PAGE) {
window.location = window.location.origin + '?' + updatedParams;
} else {
window.location = window.location.origin + window.location.pathname + '?' + updatedParams;
}
}
}
}
/**
* @private
*/
_handleOpenGalleryImages()
{
let images = $('div.gdtl a > img, div.gdtm a > img');
let firstPage = images.first().attr('alt');
let firstPageNumber = Number(firstPage);
let paddedPageLength = firstPage.length;
let maxPages = firstPageNumber + images.length - 1;
for (let page = maxPages; page >= firstPageNumber; page--) {
let paddedPage = page.toString().padStart(paddedPageLength, '0');
window.open(images.filter('[alt="' + paddedPage + '"]').parent().attr('href'));
}
if (this._getConfig(UI_AUTO_NEXT_ON_OPEN_ALL_IMAGES)) {
let page = window.location.href.split('=')[1] || 0;
let pageNavs = $('.ptt td');
let maxPages = Number.parseInt(pageNavs.eq(pageNavs.length - 2).children('a').text()) - 1;
if (page < maxPages) {
let uri = window.location.href;
if (page === 0) {
uri += '?p=1';
} else {
uri = uri.replace('?p=' + page++, '?p=' + page);
}
window.location = uri;
}
}
}
/**
* @private
*/
_setupEvents()
{
this._onValidateInit = () => !IS_SMALL_WINDOW;
this._onUIBuild(() => {
this._performOperation(UI_VISITED_HIGHLIGHT, () => {
GM_addStyle(`td.gl2e > div > a:visited > .glname > .glink {color: black;}`);
});
if (IS_SEARCH_PAGE) {
this._handleDefaults();
}
});
this._onUIBuilt(() => this._uiGen.getSelectedSection()[0].userScript = this);
this._onItemHide = (item) => {
if (item.is('td.gl2e')) {
item.parent().addClass('noncompliant-item');
item.parent().hide();
} else {
item.removeClass('noncompliant-item');
item.hide();
}
};
this._onItemShow = (item) => {
if (item.is('td.gl2e')) {
item.parent().removeClass('noncompliant-item');
item.parent().show();
} else {
item.removeClass('noncompliant-item');
item.show();
}
};
}
/**
* @private
*/
_setupFeatures()
{
this._configurationManager.
addFlagField(
UI_AUTO_NEXT_ON_OPEN_ALL_IMAGES, 'Automatically navigates to the next page after opening all images.').
addFlagField(
UI_VISITED_HIGHLIGHT, 'Colours the visited gallery links black, to make them distinct.').
addRangeField(
UI_DEFAULTS_PAGE_RANGE, 0, 2000, 'Enable default page range filter in searches.').
addFlagField(
UI_DEFAULTS_PAGE_RANGE_ENABLE,
'Always set these page limits in searches. Ignored if you set your own values on the page.').
addRadiosGroup(
UI_DEFAULTS_RATING,
[
['2 stars', '2'],
['3 stars', '3'],
['4 stars', '4'],
['5 stars', '5'],
],
'Always set this rating filter in searches. Ignored if you set your own value on the page.').
addFlagField(
UI_DEFAULTS_RATING_ENABLE, 'Enable default rating filter in searches').
addRulesetField(
UI_DEFAULTS_TAGS,
5,
'Always add the following tags in search. Can be overridden with at least one tag present.').
addFlagField(
UI_DEFAULTS_TAGS_ENABLE, 'Enable default tags in searches.');
let otherTagSections = IS_GALLERY_PAGE ? $('#taglist') : null;
this._addItemTagHighlights(
UI_FAVOURITE_TAGS, otherTagSections, 'favourite-tag', 'Specify favourite tags to highlight.', 10);
this._addItemTagHighlights(
UI_DISLIKED_TAGS, otherTagSections, 'disliked-tag', 'Specify disliked tags to highlight.', 10);
this._addItemTagBlacklistFilter(20);
}
/**
* @private
*/
_setupUI()
{
let openGalleryPages = [];
if (IS_GALLERY_PAGE) {
openGalleryPages = [
this._uiGen.createSeparator(),
this._uiGen.createFormButton(
'Open Gallery Images',
'Opens all images on current page of this gallery.',
() => this._handleOpenGalleryImages()
),
this._uiGen.createBreakSeparator(),
this._configurationManager.createElement(UI_AUTO_NEXT_ON_OPEN_ALL_IMAGES),
];
}
this._userInterface = [
this._uiGen.createTabsSection(['Filters', 'Highlights', 'Defaults', 'Global'], [
this._uiGen.createTabPanel('Filters', true).append([
this._configurationManager.createElement(OPTION_ENABLE_TAG_BLACKLIST),
this._configurationManager.createElement(FILTER_TAG_BLACKLIST),
]),
this._uiGen.createTabPanel('Highlights').append([
this._configurationManager.createElement(UI_FAVOURITE_TAGS),
this._configurationManager.createElement(UI_DISLIKED_TAGS),
]),
this._uiGen.createTabPanel('Defaults').append([
this._configurationManager.createElement(UI_DEFAULTS_PAGE_RANGE_ENABLE),
this._configurationManager.createElement(UI_DEFAULTS_PAGE_RANGE),
this._uiGen.createSeparator(),
this._configurationManager.createElement(UI_DEFAULTS_RATING),
this._uiGen.createBreakSeparator(),
this._configurationManager.createElement(UI_DEFAULTS_RATING_ENABLE),
this._uiGen.createSeparator(),
this._configurationManager.createElement(UI_DEFAULTS_TAGS_ENABLE),
this._configurationManager.createElement(UI_DEFAULTS_TAGS),
]),
this._uiGen.createTabPanel('Global').append([
this._configurationManager.createElement(UI_VISITED_HIGHLIGHT),
this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
this._uiGen.createSeparator(),
this._createSettingsBackupRestoreFormActions(),
]),
]),
this._uiGen.createStatisticsFormGroup(FILTER_TAG_BLACKLIST),
...openGalleryPages,
this._uiGen.createSeparator(),
this._createSettingsFormActions(),
this._uiGen.createSeparator(),
this._uiGen.createStatusSection(),
];
}
}
(new EHentaiSearchAndUITweaks).init();