xHamster Tweaks

Adds HD and Newest quick filter buttons on pornstar/creator profiles + auto-shows description and date under the video.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         xHamster Tweaks
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Adds HD and Newest quick filter buttons on pornstar/creator profiles + auto-shows description and date under the video.
// @author       nereids
// @match        https://xhamster.com/pornstars/*
// @match        https://xhamster.com/creators/*
// @match        https://xhamster.com/videos/*
// @match        https://xhamster.com/movies/*
// @icon         https://icons.duckduckgo.com/ip3/xhamster.com.ico
// @license      MIT
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // 1. Unified CSS Styles
    GM_addStyle(`
        .auto-about-info {
            width: fit-content !important;
            max-width: 100% !important;
            white-space: normal;
        }
        .xh-custom-filter-btn {
            text-decoration: none !important;
            margin-left: 2px;
        }
        #xh-custom-meta-box {
            display: inline-block;
            width: max-content;
            max-width: 100%;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 16px;
            margin-top: 12px;
            margin-bottom: 12px;
            font-family: sans-serif;
            color: #e3e3e3;
            box-sizing: border-box;
        }
        .xh-meta-desc {
            font-size: 13px;
            word-wrap: break-word;
            margin-bottom: 10px;
            line-height: 1.5;
            white-space: normal;
        }
        .xh-meta-subline {
            display: flex;
            gap: 15px;
            font-size: 13px;
            color: #a0a0a0;
        }
        .xh-meta-subline a {
            color: #ff8c00;
            text-decoration: none;
        }
    `);

    // 2. SVG Assets
    const svgs = {
        hd: {
            default: `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M300-440h80v50q0 13 8.5 21.5T410-360q13 0 21.5-8.5T440-390v-180q0-13-8.5-21.5T410-600q-13 0-21.5 8.5T380-570v70h-80v-70q0-13-8.5-21.5T270-600q-13 0-21.5 8.5T240-570v180q0 13 8.5 21.5T270-360q13 0 21.5-8.5T300-390v-50Zm240 80h140q17 0 28.5-11.5T720-400v-160q0-17-11.5-28.5T680-600H540q-8 0-14 6t-6 14v200q0 8 6 14t14 6Zm40-60v-120h80v120h-80ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z"/></svg>`,
            selected: `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M300-446.15h92.31V-390q0 10.31 6.77 17.08 6.77 6.77 17.07 6.77 10.31 0 17.08-6.77Q440-379.69 440-390v-180q0-10.31-6.77-17.08-6.77-6.77-17.08-6.77-10.3 0-17.07 6.77T392.31-570v76.15H300V-570q0-10.31-6.77-17.08-6.77-6.77-17.08-6.77-10.3 0-17.07 6.77T252.31-570v180q0 10.31 6.77 17.08 6.77 6.77 17.07 6.77 10.31 0 17.08-6.77Q300-379.69 300-390v-56.15Zm238.08 80h128.07q17.39 0 29.46-12.08 12.08-12.08 12.08-29.46v-144.62q0-17.38-12.08-29.46-12.07-12.08-29.46-12.08H538.08q-7.23 0-12.66 5.43Q520-583 520-575.77v191.54q0 7.23 5.42 12.65 5.43 5.43 12.66 5.43Zm29.61-47.7v-132.3h86.16q2.3 0 4.23 1.92Q660-542.31 660-540v120q0 2.31-1.92 4.23-1.93 1.92-4.23 1.92h-86.16ZM172.31-180Q142-180 121-201q-21-21-21-51.31v-455.38Q100-738 121-759q21-21 51.31-21h615.38Q818-780 839-759q21 21 21 51.31v455.38Q860-222 839-201q-21 21-51.31 21H172.31Z"/></svg>`
        },
        new: {
            default: `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M120-160q-33 0-56.5-23.5T40-240v-480q0-33 23.5-56.5T120-800h720q33 0 56.5 23.5T920-720v480q0 33-23.5 56.5T840-160H120Zm0-80h720v-480H120v480Zm70-260 95 130q3 5 8 7.5t11 2.5h12q10 0 17-7t7-17v-191q0-11-7-18t-18-7q-11 0-18 7t-7 18v115l-93-130q-4-5-9-7.5t-11-2.5h-12q-11 0-18 7t-7 18v190q0 11 7 18t18 7q11 0 18-7t7-18v-115Zm210 140h115q11 0 18-7t7-18q0-11-7-18t-18-7h-75v-44h75q11 0 18-7t7-18q0-11-7-18t-18-7h-75v-46h75q11 0 18-7t7-18q0-11-7-18t-18-7H400q-8 0-14 6t-6 14v200q0 8 6 14t14 6Zm220 0h160q17 0 28.5-11.5T820-400v-175q0-11-7-18t-18-7q-11 0-18 7t-7 18v155h-44v-115q0-11-7-18t-18-7q-11 0-18 7t-7 18v115h-46v-155q0-11-7-18t-18-7q-11 0-18 7t-7 18v175q0 17 11.5 28.5T620-360ZM120-240v-480 480Z"/></svg>`,
            selected: `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M132.31-180Q102-180 81-201q-21-21-21-51.31v-455.38Q60-738 81-759q21-21 51.31-21h695.38Q858-780 879-759q21 21 21 51.31v455.38Q900-222 879-201q-21 21-51.31 21H132.31Zm56.92-336.16L288.46-375q2.62 4.23 6.86 6.54 4.24 2.31 9.33 2.31h10.18q8.48 0 14.52-5.99 6.03-5.99 6.03-14.55v-186.39q0-9.14-5.8-14.95-5.8-5.82-14.92-5.82-9.12 0-14.97 5.82-5.84 5.81-5.84 14.95v129.24L197-585q-3.23-4.61-7.75-6.73t-9.94-2.12h-10.26q-9.39 0-15.38 6.03-5.98 6.04-5.98 15.51v185.39q0 9.14 5.81 14.95 5.8 5.82 14.92 5.82 9.12 0 14.96-5.82 5.85-5.81 5.85-14.95v-129.24Zm215 150.01h108.85q9.14 0 14.95-5.81 5.82-5.8 5.82-14.92 0-9.12-5.82-14.97-5.81-5.84-14.95-5.84h-79.23v-50.16h79.23q9.14 0 14.95-5.8 5.82-5.8 5.82-14.92 0-9.12-5.82-14.97-5.81-5.84-14.95-5.84h-79.23v-52.93h79.23q9.14 0 14.95-5.8 5.82-5.8 5.82-14.92 0-9.12-5.82-14.97-5.81-5.85-14.95-5.85H404.23q-7.23 0-12.65 5.43-5.43 5.42-5.43 12.65v191.54q0 7.23 5.43 12.65 5.42 5.43 12.65 5.43Zm214.23 0h160q14.69 0 24.27-9.58t9.58-24.27v-173.08q0-9.14-5.81-14.95-5.8-5.82-14.92-5.82-9.12 0-14.96 5.82-5.85 5.81-5.85 14.95v159.23h-50.92v-119.23q0-9.14-5.81-14.95-5.8-5.82-14.92-5.82-9.12 0-14.97 5.82-5.84 5.81-5.84 14.95v119.23h-52.16v-159.23q0-9.14-5.8-14.95-5.8-5.82-14.92-5.82-9.12 0-14.97 5.82-5.84 5.81-5.84 14.95V-400q0 14.69 9.57 24.27 9.58 9.58 24.27 9.58Z"/></svg>`
        }
    };

    // 3. Logic for Profile Quick Filters
    function getBaseUrl() {
        const parts = window.location.pathname.split('/').filter(Boolean);
        return `/${parts[0]}/${parts[1]}`;
    }

    function createCustomButton(text, href, svgIcon, baseClassNames) {
        const btn = document.createElement('a');
        btn.href = href;
        btn.className = `${baseClassNames} xh-custom-filter-btn`;
        btn.innerHTML = `
            <span class="icon-container" style="display: flex; align-items: center;">${svgIcon}</span>
            <span class="body-bold-8643e primary-8643e">${text}</span>
        `;
        return btn;
    }

    function handleProfileFilters() {
        if (document.querySelector('.xh-custom-filter-btn')) return;

        const buttons = Array.from(document.querySelectorAll('button.container-a4c1b.button-a4c1b'));
        const filterButton = buttons.find(btn => btn.textContent.trim().includes('Filters'));

        if (filterButton) {
            // Evaluates path ending with target or target followed immediately by a slash (for pages like /hd/2)
            const isHDActive = /\/hd(\/|$)/.test(window.location.pathname);
            const isNewActive = /\/newest(\/|$)/.test(window.location.pathname);

            const baseUrl = getBaseUrl();
            const btnClasses = filterButton.className;

            const hdSvg = isHDActive ? svgs.hd.selected : svgs.hd.default;
            const newSvg = isNewActive ? svgs.new.selected : svgs.new.default;

            const hdBtn = createCustomButton('HD', `${baseUrl}/hd`, hdSvg, btnClasses);
            const newBtn = createCustomButton('New', `${baseUrl}/newest`, newSvg, btnClasses);

            filterButton.parentNode.insertBefore(hdBtn, filterButton.nextSibling);
            filterButton.parentNode.insertBefore(newBtn, hdBtn.nextSibling);
        }
    }

    // 4. Logic for Auto-Displaying Video Meta Details
    function handleVideoMetaDisplay() {
        if (document.getElementById('xh-custom-meta-box')) return;

        const controlsElement = document.querySelector('div.controls');
        const abInfo = document.querySelector('div.ab-info[data-role="controls-info"]');

        if (controlsElement && abInfo) {
            const descEl = abInfo.querySelector('.controls-info__description');
            //Uncomment below if you want uploader name in the about box
            //const authorEl = abInfo.querySelector('.entity-info-container__author');
            const dateEl = abInfo.querySelector('.entity-info-container__date');

            if (descEl && dateEl) {
                const metaBox = document.createElement('div');
                metaBox.id = 'xh-custom-meta-box';

                let sublineHTML = '';
                //Uncomment below if you want uploader name in the about box
                //if (authorEl) {
                //    sublineHTML += `<span>${authorEl.innerHTML.trim()}</span>`;
                //}
                sublineHTML += `<span>${dateEl.textContent.trim()}</span>`;
                if(dateEl.getAttribute('data-tooltip')) {
                     sublineHTML += `<span>${dateEl.getAttribute('data-tooltip')}</span>`;
                }

                metaBox.innerHTML = `
                    <div class="xh-meta-desc">${descEl.innerHTML.trim()}</div>
                    <div class="xh-meta-subline">${sublineHTML}</div>
                `;

                controlsElement.parentNode.insertBefore(metaBox, controlsElement.nextSibling);
            }
        }
    }

    // 5. Universal Observer Loop
    const observer = new MutationObserver(() => {
        const path = window.location.pathname;
        if (path.includes('/pornstars/') || path.includes('/creators/')) {
            handleProfileFilters();
        }
        if (path.includes('/videos/') || path.includes('/movies/')) {
            handleVideoMetaDisplay();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();