Stripchat 黑名单

增强Stripchat体验:查找主播信息和隐藏不感兴趣的主播

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Stripchat 黑名单
// @namespace    https://greasyfork.org/fr/users/1468290-payamarre
// @version      1.1
// @license MIT
// @description  增强Stripchat体验:查找主播信息和隐藏不感兴趣的主播
// @author       NoOne
// @match        https://stripchat.com/*
// @match        https://*.stripchat.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @icon         https://stripchat.com/favicon.ico
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // ================ 公共功能 ================
    const common = {
        getModelName() {
            const path = window.location.pathname.split('/');
            const model = path[1];
            if (model && !['female', 'male', 'trans', 'new', 'tags', 'login', 'signup'].includes(model)) {
                return model;
            }
            return null;
        },

        createButton(id, svg, onClick, className = '') {
            const a = document.createElement('a');
            a.href = '#';
            a.className = className;
            a.innerHTML = svg;
            a.id = id;
            a.addEventListener('click', e => {
                e.preventDefault();
                onClick();
            });
            return a;
        }
    };

    // ================ 查找主播信息功能 ================
    function initFindMore() {
        let buttonsInserted = false;

        function insertButtons() {
            if (buttonsInserted) return true;

            const modelName = common.getModelName();
            if (!modelName) return false;

            const targetWrapper = document.querySelector('.view-cam-buttons-wrapper');
            if (!targetWrapper || !targetWrapper.parentNode) return false;

            ['scfinder-button', 'recume-button', 'dodao-button', 'search-button'].forEach(id => {
                const oldBtn = document.getElementById(id);
                if (oldBtn) oldBtn.remove();
            });

            const scfinderBtn = common.createButton(
                'scfinder-button',
                `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
                </svg>`,
                () => window.open(`https://camgirlfinder.net/models/sc/${modelName}`, '_blank'),
                'enhanced-button'
            );

            const recumeBtn = common.createButton(
                'recume-button',
                `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
                  <path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
                </svg>`,
                () => window.open(`https://recu.me/performer/${modelName}`, '_blank'),
                'enhanced-button'
            );

            const dodaoBtn = common.createButton(
                'dodao-button',
                `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z" />
                </svg>`,
                () => window.open(`https://dodao.xyz/?cat=&s=${modelName}`, '_blank'),
                'enhanced-button'
            );

            const searchBtn = common.createButton(
                'search-button',
                `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
                  <path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
                </svg>`,
                () => {
                    const urls = [
                        `https://www.google.com/search?q=%22${modelName}%22&num=10&uact=5`,
                        `https://yandex.com/search/?text=%22${modelName}%22`,
                        `https://recu.me/performer/${modelName}`,
                        `https://camgirlfinder.net/models/sc/${modelName}`,
                        `https://btdig.com/search?order=0&q="${modelName}"`,
                        `https://dodao.xyz/?cat=&s=${modelName}`
                    ];
                    urls.forEach((url, index) => {
                        setTimeout(() => window.open(url, '_blank'), index * 200);
                    });
                },
                'enhanced-button'
            );

            const buttonGroup = document.createElement('div');
            buttonGroup.style.display = 'flex';
            buttonGroup.style.gap = '18px';
            buttonGroup.style.alignItems = 'center';
            buttonGroup.appendChild(dodaoBtn);
            buttonGroup.appendChild(scfinderBtn);
            buttonGroup.appendChild(recumeBtn);
            buttonGroup.appendChild(searchBtn);

            targetWrapper.parentNode.insertBefore(buttonGroup, targetWrapper);

            buttonsInserted = true;
            return true;
        }

        const observer = new MutationObserver(() => insertButtons());
        observer.observe(document.body, { childList: true, subtree: true });
        setTimeout(insertButtons, 1000);
    }

    // ================ 隐藏不感兴趣主播功能 ================
    function initHideModels() {
        // 存储不感兴趣的主播列表
        let blockedModels = GM_getValue('blockedModels', {});

        // 创建管理面板按钮
        function createToggleButton() {
            const toggleBtn = document.createElement('button');
            toggleBtn.className = 'blocked-models-toggle';
            toggleBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="20" height="20">
                <path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" />
            </svg>`;
            toggleBtn.title = '管理已隐藏主播';
            toggleBtn.addEventListener('click', toggleBlockedModelsPanel);
            document.body.appendChild(toggleBtn);
        }

        // 创建管理面板
        function createBlockedModelsPanel() {
            const panel = document.createElement('div');
            panel.className = 'blocked-models-panel';
            panel.id = 'blocked-models-panel';

            const header = document.createElement('div');
            header.className = 'blocked-models-header';

            const title = document.createElement('h3');
            title.textContent = '已隐藏的主播';

            const closeBtn = document.createElement('button');
            closeBtn.className = 'unblock-btn';
            closeBtn.textContent = '关闭';
            closeBtn.addEventListener('click', () => {
                document.getElementById('blocked-models-panel').style.display = 'none';
            });

            header.appendChild(title);
            header.appendChild(closeBtn);

            const content = document.createElement('div');
            content.className = 'blocked-models-content';

            panel.appendChild(header);
            panel.appendChild(content);
            document.body.appendChild(panel);

            updateBlockedModelsList();
        }

        // 切换管理面板显示
        function toggleBlockedModelsPanel() {
            const panel = document.getElementById('blocked-models-panel');
            if (panel.style.display === 'block') {
                panel.style.display = 'none';
            } else {
                updateBlockedModelsList();
                panel.style.display = 'block';
            }
        }

        // 更新已隐藏主播列表
        function updateBlockedModelsList() {
            const content = document.querySelector('.blocked-models-content');
            content.innerHTML = '';

            const modelCount = Object.keys(blockedModels).length;

            if (modelCount === 0) {
                const emptyMsg = document.createElement('p');
                emptyMsg.textContent = '您还没有隐藏任何主播';
                emptyMsg.style.color = '#aaa';
                emptyMsg.style.textAlign = 'center';
                content.appendChild(emptyMsg);
                return;
            }

            for (const modelId in blockedModels) {
                const modelItem = document.createElement('div');
                modelItem.className = 'blocked-model-item';

                const modelName = document.createElement('span');
                modelName.className = 'blocked-model-name';
                modelName.textContent = blockedModels[modelId].name;

                const unblockBtn = document.createElement('button');
                unblockBtn.className = 'unblock-btn';
                unblockBtn.textContent = '取消隐藏';
                unblockBtn.dataset.modelId = modelId;
                unblockBtn.addEventListener('click', function() {
                    unblockModel(this.dataset.modelId);
                });

                modelItem.appendChild(modelName);
                modelItem.appendChild(unblockBtn);
                content.appendChild(modelItem);
            }
        }

        // 添加隐藏按钮到主播缩略图
        function addHideButtons() {
            const modelItems = document.querySelectorAll('.model-list-item:not(.processed-for-hiding)');

            modelItems.forEach(item => {
                // 标记为已处理
                item.classList.add('processed-for-hiding');

                // 获取主播ID和名称
                const linkEl = item.querySelector('a[href*="/"]');
                if (!linkEl) return;

                const href = linkEl.getAttribute('href');
                const modelId = href.split('/').pop();

                // 如果已经在屏蔽列表中,隐藏
                if (blockedModels[modelId]) {
                    item.style.display = 'none';
                    return;
                }

                // 创建隐藏按钮
                const hideBtn = document.createElement('button');
                hideBtn.className = 'hide-model-btn';
                hideBtn.innerHTML = '&times;';
                hideBtn.title = '隐藏此主播';
                hideBtn.dataset.modelId = modelId;

                // 获取主播名称
                const nameEl = item.querySelector('.model-name') || item.querySelector('a[href*="/"]');
                const modelName = nameEl ? nameEl.textContent.trim() : modelId;
                hideBtn.dataset.modelName = modelName;

                hideBtn.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    blockModel(this.dataset.modelId, this.dataset.modelName);
                });

                item.style.position = 'relative';
                item.appendChild(hideBtn);
            });
        }

        // 隐藏主播
        function blockModel(modelId, modelName) {
            blockedModels[modelId] = {
                name: modelName,
                blockedAt: new Date().toISOString()
            };

            GM_setValue('blockedModels', blockedModels);

            // 隐藏所有匹配的主播元素
            const modelItems = document.querySelectorAll(`.model-list-item`);
            modelItems.forEach(item => {
                const link = item.querySelector('a[href*="/"]');
                if (link && link.getAttribute('href').endsWith(`/${modelId}`)) {
                    item.style.display = 'none';
                }
            });

            // 如果面板打开,更新列表
            if (document.getElementById('blocked-models-panel').style.display === 'block') {
                updateBlockedModelsList();
            }
        }

        // 取消隐藏主播
        function unblockModel(modelId) {
            delete blockedModels[modelId];
            GM_setValue('blockedModels', blockedModels);

            // 重新显示匹配的主播元素
            const modelItems = document.querySelectorAll(`.model-list-item`);
            modelItems.forEach(item => {
                const link = item.querySelector('a[href*="/"]');
                if (link && link.getAttribute('href').endsWith(`/${modelId}`)) {
                    item.style.display = '';
                }
            });

            updateBlockedModelsList();
        }

        // 初始化隐藏主播功能
        function initHideFeature() {
            createToggleButton();
            createBlockedModelsPanel();

            // 初始隐藏已屏蔽主播
            addHideButtons();

            // 监听DOM变化,为新加载的主播添加隐藏按钮
            const observer = new MutationObserver(mutations => {
                let needToAddButtons = false;

                mutations.forEach(mutation => {
                    if (mutation.addedNodes.length) {
                        needToAddButtons = true;
                    }
                });

                if (needToAddButtons) {
                    addHideButtons();
                }
            });

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

        // 初始化隐藏主播功能
        initHideFeature();
    }

    // ================ 样式 ================
    function addStyles() {
        const styles = `
            /* 查找更多信息按钮样式 */
            .enhanced-button {
                display: inline-flex !important;
                justify-content: center;
                align-items: center;
                width: 40px;
                height: 40px;
                border-radius: 50%;
                border: 2px solid #feb601;
                background-color: inherit;
                color: inherit;
                transition: background-color 0.2s, color 0.2s, stroke 0.2s;
                cursor: pointer;
            }

            .enhanced-button:hover {
                background-color: #feb601;
                border: 2px solid #feb601;
            }

            .enhanced-button:hover svg {
                stroke: black;
            }

            .enhanced-button svg {
                width: 24px;
                height: 24px;
                stroke: currentColor;
            }

            /* 隐藏主播按钮样式 */
            .hide-model-btn {
                position: absolute;
                top: 35px;
                right: 8px;
                z-index: 100;
                background-color: #f03e3e;
                color: white;
                border: none;
                border-radius: 50%;
                width: 24px;
                height: 24px;
                font-size: 14px;
                line-height: 1;
                cursor: pointer;
                opacity: 0;
                transition: opacity 0.2s;
                display: flex;
                align-items: center;
                justify-content: center;
            }

            .model-list-item:hover .hide-model-btn {
                opacity: 0.8;
            }

            .hide-model-btn:hover {
                opacity: 1 !important;
                background-color: #e03131;
            }

            /* 管理面板样式 */
            .blocked-models-panel {
                position: fixed;
                top: 70px;
                right: 20px;
                width: 300px;
                max-height: 400px;
                background-color: #1a1a1a;
                border: 1px solid #333;
                border-radius: 8px;
                z-index: 10000;
                overflow: hidden;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
                display: none;
            }

            .blocked-models-header {
                padding: 10px 15px;
                background-color: #2a2a2a;
                border-bottom: 1px solid #333;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            .blocked-models-header h3 {
                margin: 0;
                color: #feb601;
                font-size: 16px;
            }

            .blocked-models-content {
                padding: 10px 15px;
                max-height: 300px;
                overflow-y: auto;
            }

            .blocked-model-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 8px 0;
                border-bottom: 1px solid #333;
            }

            .blocked-model-item:last-child {
                border-bottom: none;
            }

            .blocked-model-name {
                color: #fff;
                font-size: 14px;
            }

            .unblock-btn {
                background-color: #feb601;
                color: #000;
                border: none;
                border-radius: 4px;
                padding: 3px 8px;
                font-size: 12px;
                cursor: pointer;
            }

            .unblock-btn:hover {
                background-color: #ffcc33;
            }

            .blocked-models-toggle {
                position: fixed;
                top: 7px;
                right: 350px;
                background-color: #a2252d;
                color: #fff;
                border: none;
                border-radius: 50%;
                width: 40px;
                height: 40px;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                z-index: 9999;
            }

            .blocked-models-toggle svg {
                stroke: #fff;
            }

            .blocked-models-toggle:hover {
                background-color: #c42a35;
            }
        `;

        const styleEl = document.createElement('style');
        styleEl.textContent = styles;
        document.head.appendChild(styleEl);
    }

    // ================ 初始化 ================
    function init() {
        // 添加样式
        addStyles();

        // 初始化查找主播信息功能
        initFindMore();

        // 初始化隐藏主播功能
        initHideModels();
    }

    // 等待页面加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();