Sleazy Fork is available in English.

SpankBang - Search and UI Enhancer

Various search filters and user experience enhancers

目前為 2021-09-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         SpankBang - Search and UI Enhancer
// @namespace    brazenvoid
// @version      1.1.0
// @author       brazenvoid
// @license      GPL-3.0-only
// @description  Various search filters and user experience enhancers
// @match        https://spankbang.com/*
// @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=899286
// @require      https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=899448
// @require      https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=892799
// @require      https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=899428
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

GM_addStyle(
    `button.show-settings{height:60vh;top:20vh}#settings-wrapper{top:20vh;width:300px}.bg-brand{background-color:#69150d}.font-primary{color:white}.font-secondary{color:black}hr{background:white}`);

const PAGE_PATH_NAME = window.location.pathname;
const PAGE_PATH_FRAGMENTS = PAGE_PATH_NAME.split('/');

const IS_HOME_PAGE = PAGE_PATH_NAME === '/';
const IS_VIDEO_PAGE = PAGE_PATH_FRAGMENTS[2] === 'video' || PAGE_PATH_FRAGMENTS[2] === 'playlist';

const ITEM_CLASSES = 'div.video-item';
const ITEM_LISTS_SELECTOR = 'div.video-list';
const ITEM_TEXT_NODE_SELECTOR = 'a.n';

const SCRIPT_PREFIX = 'sb-sui-';

// Configuration

const FILTER_VIDEOS_SD = 'Show Only HD Videos';
const FILTER_VIDEOS_DURATION = 'Duration';
const FILTER_VIDEOS_MAXIMUM_AGE = 'Maximum Age';
const FILTER_VIDEOS_MAXIMUM_AGE_UNIT = 'Maximum Age Unit';
const FILTER_VIDEOS_MINIMUM_AGE = 'Minimum Age';
const FILTER_VIDEOS_MINIMUM_AGE_UNIT = 'Minimum Age Unit';
const FILTER_VIDEOS_RATING = 'Rating';
const FILTER_VIDEOS_VIEWS = 'Views';

const UI_REMOVE_AD_BOX_VIDEO_LISTS = 'Remove Ad in Video Lists';
const UI_REMOVE_EMBED_VIDEO_SECTION = 'Remove Embed Video Section';
const UI_REMOVE_LIVE_MODEL_SECTIONS = 'Remove Live Model Sections';
const UI_REDIRECT_SUBSCRIPTIONS_LINK = 'Redirect Subscriptions Link';
const UI_REPOSITION_SCREENSHOTS = 'Reposition Video Screenshots';
const UI_REPOSITION_VIDEO_DETAILS = 'Reposition Video Details';
const UI_SWAP_RELATED_VIDEOS = 'Reposition Related Videos';

// Item attributes

const ITEM_AGE = 'scriptAge';
const ITEM_DURATION = 'scriptDuration';
const ITEM_RATING = 'scriptRating';
const ITEM_VIEWS = 'scriptViews';

class SpankBangSearchAndUIEnhancer extends BrazenBaseSearchEnhancer
{
    constructor()
    {
        super(SCRIPT_PREFIX, ITEM_CLASSES);

        this._configurationManager.
            addFlagField(FILTER_VIDEOS_SD, 'Filter videos of less than 720p resolution.').
            addFlagField(UI_REMOVE_AD_BOX_VIDEO_LISTS, 'Removes the ad box found in the first row of videos lists.').
            addFlagField(UI_REMOVE_EMBED_VIDEO_SECTION, 'Removes embed video section on video pages.').
            addFlagField(UI_REMOVE_LIVE_MODEL_SECTIONS, 'Removes live model sections from the site.').
            addFlagField(UI_REDIRECT_SUBSCRIPTIONS_LINK, 'Redirects subscription videos page shortcut on user bar to new subscribed videos view.').
            addFlagField(UI_REPOSITION_SCREENSHOTS, 'Move video screenshots above video player on video pages.').
            addFlagField(UI_REPOSITION_VIDEO_DETAILS, 'Move video details section to the top of the right pane.').
            addFlagField(UI_SWAP_RELATED_VIDEOS, 'Swaps related videos section with comments section').
            addNumberField(FILTER_VIDEOS_MAXIMUM_AGE, 0, 100, 'Maximum age filter value.').
            addNumberField(FILTER_VIDEOS_MINIMUM_AGE, 0, 100, 'Minimum age filter value.').
            addRangeField(FILTER_VIDEOS_DURATION, 0, 100000, 'Filter videos by duration in minutes.').
            addRangeField(FILTER_VIDEOS_RATING, 0, 100, 'Filter videos by duration.').
            addRangeField(FILTER_VIDEOS_VIEWS, 0, 9999999999, 'Filter videos by view count.');
//            addSelectField(FILTER_VIDEOS_MAXIMUM_AGE_UNIT, [
//                ['Minutes', 'minutes'],
//                ['Hours', 'hours'],
//                ['Days', 'days'],
//                ['Weeks', 'weeks'],
//                ['Months', 'months'],
//                ['Years', 'years]'],
//            ], 'Maximum age filter unit.').
//            addSelectField(FILTER_VIDEOS_MINIMUM_AGE_UNIT, [
//                ['Minutes', 'minutes'],
//                ['Hours', 'hours'],
//                ['Days', 'days'],
//                ['Weeks', 'weeks'],
//                ['Months', 'months'],
//                ['Years', 'years]'],
//            ], 'Minimum age filter unit.')

//        this._addPaginationConfiguration()
        this._setupUI();
        this._setupCompliance();
        this._setupComplianceFilters();
    }

    _analyseItem(item)
    {
        let stats = item.find('div.stats');
        if (stats.length) {
            item[0][ITEM_AGE] = stats.find('span.d').text();
            item[0][ITEM_VIEWS] = stats.find('span.v').text();
            item[0][ITEM_RATING] = stats.find('span.r').text();
        } else {
            item[0][ITEM_AGE] = null;
            item[0][ITEM_RATING] = null;
            item[0][ITEM_VIEWS] = null;
        }
        item[0][ITEM_DURATION] = parseInt(item.find('span.l').text().replace('m', ''));
    }

    /**
     * @private
     */
    _redirectSubscriptionsLink()
    {
        if (this._configurationManager.getValue(UI_REDIRECT_SUBSCRIPTIONS_LINK)) {
            $('a[href="/users/social"]').attr('href', '/users/social?sort=new');
        }
    }

    _removeAdBoxInVideoLists()
    {
        if (this._configurationManager.getValue(UI_REMOVE_AD_BOX_VIDEO_LISTS)) {
            $(ITEM_CLASSES + ' ins').parent().remove();
        }
    }

    /**
     * @private
     */
    _removeEmbedVideoSection()
    {
        if (this._configurationManager.getValue(UI_REMOVE_EMBED_VIDEO_SECTION)) {
            $('.embed').remove();
        }
    }

    /**
     * @private
     */
    _removeLiveModelSections()
    {
        if (this._configurationManager.getValue(UI_REMOVE_EMBED_VIDEO_SECTION)) {
            let liveModelsSection = $('.lv_cm_cl_mx_why');
            if (IS_HOME_PAGE) {
                liveModelsSection.prev().remove();
                liveModelsSection.next().remove();
            }
            liveModelsSection.remove();
        }
    }

    /**
     * @return {JQuery<HTMLElement> | jQuery | HTMLElement}
     * @private
     */
    _generatePseudoLeftSection()
    {
        return $('<div class="left" style="padding: 23px 0 0 0"></div>');
    }

    /**
     * @private
     */
    _repositionVideoDetails()
    {
        if (this._configurationManager.getValue(UI_REPOSITION_VIDEO_DETAILS)) {
            let rightPane = $('div.right');

            let pseudoLeftSection = this._generatePseudoLeftSection();
            rightPane.prepend(pseudoLeftSection);
            pseudoLeftSection.append($('div.left section.details'));
        }
    }

    /**
     * @private
     */
    _repositionVideoScreenshots()
    {
        if (this._configurationManager.getValue(UI_REPOSITION_SCREENSHOTS)) {
            $('div.left section.timeline').insertBefore('#player_wrapper_outer');
        }
    }

    /**
     * @private
     */
    _setupCompliance()
    {
        this._onGetItemLists = () => $(ITEM_LISTS_SELECTOR);

        this._onGetItemName = (videoItem) => videoItem.find(ITEM_TEXT_NODE_SELECTOR).text();

        this._onFirstHitBeforeCompliance = (item) => this._analyseItem(item);

        this._onFirstHitAfterCompliance = (item) => {
            Validator.sanitizeTextNode(item.find(ITEM_TEXT_NODE_SELECTOR), this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized);
        };
    }

    /**
     * @private
     */
    _setupComplianceFilters()
    {
        this._addItemTextSanitizationFilter(
            'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. Example Rule: boyfriend=stepson,stepdad');

        this._addItemWhitelistFilter('Show videos with specified phrases in their names. Separate the phrases with line breaks.');

        this._addItemTextSearchFilter();

        this._addItemComplianceFilter(FILTER_VIDEOS_RATING, (item, range) =>
            item[0][ITEM_RATING] ? Validator.isInRange(item[0][ITEM_RATING], range.minimum, range.maximum) : true);

        this._addItemComplianceFilter(FILTER_VIDEOS_DURATION, (item, range) =>
            Validator.isInRange(item[0][ITEM_DURATION], range.minimum, range.maximum))

        this._addItemComplianceFilter(FILTER_VIDEOS_VIEWS, (item, range) => {
            let viewsString = item[0][ITEM_VIEWS];
            if (!viewsString) {
                return true;
            }
            let viewsAmount = parseFloat(viewsString.replace('K', '').replace('M', '').replace('B', ''));
            if (viewsString.endsWith('K')) {
                viewsAmount *= 1000;
            } else {
                if (viewsString.endsWith('M')) {
                    viewsAmount *= 1000000;
                } else {
                    if (viewsString.endsWith('B')) {
                        viewsAmount *= 1000000000;
                    }
                }
            }
            return Validator.isInRange(viewsAmount, range.minimum, range.maximum);
        });

        this._addItemComplianceFilter(FILTER_VIDEOS_SD, (item) => Validator.doesChildExist(item, 'span.h'));

        this._addItemBlacklistFilter('Hide videos with specified phrases in their names. Separate the phrases with line breaks.');
    }

    /**
     * @private
     */
    _setupUI()
    {
        this._onBeforeUIBuild = () => {
            this._redirectSubscriptionsLink();
            this._removeLiveModelSections();
            this._removeAdBoxInVideoLists();
            if (IS_VIDEO_PAGE) {
                this._removeEmbedVideoSection();
                this._repositionVideoDetails();
                this._repositionVideoScreenshots();
                this._swapRelatedVideos();
                Validator.sanitizeNodeOfSelector('div.video > div.left > h1', this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized);
            }
        };

        this._onUIBuild = () =>
            this._uiGen.createSettingsSection().append([
                this._uiGen.createTabsSection(['Filters', 'Text', 'UI', 'Global', 'Stats'], [
                    this._uiGen.createTabPanel('Filters', true).append([
                        this._configurationManager.createElement(FILTER_VIDEOS_DURATION),
                        this._configurationManager.createElement(FILTER_VIDEOS_RATING),
                        this._configurationManager.createElement(FILTER_VIDEOS_VIEWS),
//                        this._configurationManager.createElement(FILTER_VIDEOS_MINIMUM_AGE),
//                        this._configurationManager.createElement(FILTER_VIDEOS_MINIMUM_AGE_UNIT),
//                        this._configurationManager.createElement(FILTER_VIDEOS_MAXIMUM_AGE),
//                        this._configurationManager.createElement(FILTER_VIDEOS_MAXIMUM_AGE_UNIT),
                        this._configurationManager.createElement(FILTER_VIDEOS_SD),
                        this._uiGen.createSeparator(),
                        this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
                    ]),
                    this._uiGen.createTabPanel('Text').append([
                        this._configurationManager.createElement(FILTER_TEXT_SEARCH),
                        this._configurationManager.createElement(FILTER_TEXT_BLACKLIST),
                        this._configurationManager.createElement(FILTER_TEXT_WHITELIST),
                        this._configurationManager.createElement(FILTER_TEXT_SANITIZATION),
                    ]),
                    this._uiGen.createTabPanel('UI').append([
                        this._configurationManager.createElement(UI_REMOVE_AD_BOX_VIDEO_LISTS),
                        this._configurationManager.createElement(UI_REDIRECT_SUBSCRIPTIONS_LINK),
                        this._configurationManager.createElement(UI_REMOVE_EMBED_VIDEO_SECTION),
                        this._configurationManager.createElement(UI_REMOVE_LIVE_MODEL_SECTIONS),
                        this._configurationManager.createElement(UI_SWAP_RELATED_VIDEOS),
                        this._configurationManager.createElement(UI_REPOSITION_VIDEO_DETAILS),
                        this._configurationManager.createElement(UI_REPOSITION_SCREENSHOTS),
                        this._uiGen.createSeparator(),
                        this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
                    ]),
                    this._uiGen.createTabPanel('Global').append([
                        this._createSettingsBackupRestoreFormActions(),
                    ]),
                    this._uiGen.createTabPanel('Stats').append([
                        this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_DURATION),
                        this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_RATING),
                        this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS),
                        this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_SD),
                        this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH),
                        this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
                        this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
                        this._uiGen.createSeparator(),
                        this._uiGen.createStatisticsTotalsGroup(),
                    ]),
                ]),
                this._createSettingsFormActions(),
                this._uiGen.createSeparator(),
                this._uiGen.createStatusSection(),
            ]);

        this._onAfterUIBuild = () => {
            this._uiGen.getSelectedSection()[0].userScript = this;
        };
    }

    /**
     * @private
     */
    _swapRelatedVideos()
    {
        if (this._configurationManager.getValue(UI_SWAP_RELATED_VIDEOS)) {
            let newRelatedVideosSection = $('<section class="user_uploads"></section>');
            newRelatedVideosSection.insertAfter('section.all_comments');
            newRelatedVideosSection.append('<h2>Similar Videos</h2>');
            newRelatedVideosSection.append($('.similar div.video-list'));
            $('.similar').remove();

            let pseudoLeftSection = this._configurationManager.getValue(UI_REPOSITION_VIDEO_DETAILS) ? $(
                'div.right div.left') : this._generatePseudoLeftSection();
            pseudoLeftSection.append($('section.all_comments'));
        }
    }
}

(new SpankBangSearchAndUIEnhancer).init();