Sleazy Fork is available in English.

9kgCoverPeek

在 M-Team 页面提取番号并显示封面图,支持尺寸切换

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         9kgCoverPeek
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  在 M-Team 页面提取番号并显示封面图,支持尺寸切换
// @author       opoa
// @match        https://*.m-team.cc/browse/adult*
// @match        https://*.m-team.cc/showcaseDetail*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @icon         https://res.cfopoa.top/icon/9k-512.webp
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 尺寸配置
    const SIZE_CONFIG = {
        small:  { maxWidth: '150px', label: '小'},
        medium: { maxWidth: '280px', label: '中'},
        large:  { maxWidth: '450px', label: '大'},
    };

    const STORAGE_KEY = 'nkg_cover_size';
    const POSITION_KEY = 'nkg_panel_position';

    const ICON_URL = 'https://res.cfopoa.top/icon/9k-512.webp';

    function getCurrentSize() {
        return GM_getValue(STORAGE_KEY, 'medium');
    }

    function setCurrentSize(size) {
        GM_setValue(STORAGE_KEY, size);
    }

    // 提取番号,支持大小写
    function extractAvCode(text) {
        const match = text.match(/([A-Za-z]{2,5})-(\d{2,5})/);
        return match ? match[0] : null;
    }

    // 创建封面图元素(含骨架屏容器)
    function createCoverImage(avCode) {
        const lowerCode = avCode.toLowerCase();
        const wrapper = document.createElement('span');
        wrapper.className = 'nkg-cover-wrapper';

        const img = document.createElement('img');
        img.src = `https://fourhoi.com/${lowerCode}/cover-n.jpg`;
        img.className = 'nkg-cover-img';
        img.loading = 'lazy';
        img.alt = avCode;

        img.addEventListener('load', () => wrapper.classList.add('nkg-loaded'), { once: true });
        img.addEventListener('error', () => {
            img.remove();
            wrapper.classList.add('nkg-error');
            wrapper.textContent = avCode;
        }, { once: true });

        wrapper.appendChild(img);
        return wrapper;
    }

    // 防重复处理
    function processSpan(span) {
        if (span.dataset.nkgProcessed) return;
        span.dataset.nkgProcessed = 'true';

        const avCode = extractAvCode(span.textContent);
        if (!avCode) return;

        const img = createCoverImage(avCode);
        span.parentNode.insertBefore(img, span);
    }

    // 切换所有封面图尺寸(通过 CSS 变量)
    function updateAllImages(size) {
        setCurrentSize(size);
        document.documentElement.style.setProperty('--nkg-cover-size', SIZE_CONFIG[size].maxWidth);
        updateSizeIndicator(size);
    }

    // 更新浮动按钮的激活状态
    function updateSizeIndicator(size) {
        const panel = document.getElementById('nkg-size-panel');
        if (!panel) return;
        panel.querySelectorAll('.nkg-btn').forEach(btn => {
            const isActive = btn.dataset.size === size;
            btn.classList.toggle('active', isActive);
            btn.setAttribute('aria-checked', isActive);
        });
    }

    // 展开/收起时自动调整位置,确保面板不超出视口
    function adjustPanelPosition(wrapper) {
        const isExpanded = wrapper.classList.contains('expanded');
        if (isExpanded) {
            wrapper._nkgOrigPos = {
                left: wrapper.style.left,
                top: wrapper.style.top,
                right: wrapper.style.right,
                bottom: wrapper.style.bottom
            };
            requestAnimationFrame(() => {
                const rect = wrapper.getBoundingClientRect();
                const fullW = wrapper.scrollWidth;
                const fullH = wrapper.scrollHeight;
                const vw = window.innerWidth;
                const vh = window.innerHeight;
                const pad = 8;
                let left = rect.left;
                let top = rect.top;
                let adjusted = false;
                if (left + fullW > vw - pad) { left = vw - fullW - pad; adjusted = true; }
                if (left < pad) { left = pad; adjusted = true; }
                if (top + fullH > vh - pad) { top = vh - fullH - pad; adjusted = true; }
                if (top < pad) { top = pad; adjusted = true; }
                if (adjusted) {
                    wrapper.classList.add('nkg-adjusting');
                    wrapper.style.left = left + 'px';
                    wrapper.style.top = top + 'px';
                    wrapper.style.right = 'auto';
                    wrapper.style.bottom = 'auto';
                    wrapper._nkgAdjusted = true;
                }
            });
        } else if (wrapper._nkgAdjusted && wrapper._nkgOrigPos) {
            const pos = wrapper._nkgOrigPos;
            wrapper.style.left = pos.left;
            wrapper.style.top = pos.top;
            wrapper.style.right = pos.right;
            wrapper.style.bottom = pos.bottom;
            wrapper._nkgAdjusted = false;
            setTimeout(() => wrapper.classList.remove('nkg-adjusting'), 380);
        }
    }

    // 使元素可拖动,拖动距离小于阈值视为点击(Pointer Events 支持触屏)
    function makeDraggable(wrapper, handle) {
        let startX, startY, origX, origY, dragging = false;

        handle.addEventListener('pointerdown', e => {
            e.preventDefault();
            handle.setPointerCapture(e.pointerId);
            startX = e.clientX;
            startY = e.clientY;
            const rect = wrapper.getBoundingClientRect();
            origX = rect.left;
            origY = rect.top;
            dragging = false;

            function onMove(e) {
                const dx = e.clientX - startX;
                const dy = e.clientY - startY;
                if (!dragging && Math.abs(dx) + Math.abs(dy) > 4) {
                    dragging = true;
                    wrapper.classList.remove('nkg-adjusting');
                }
                if (dragging) {
                    wrapper.style.left = origX + dx + 'px';
                    wrapper.style.top = origY + dy + 'px';
                    wrapper.style.right = 'auto';
                    wrapper.style.bottom = 'auto';
                }
            }

            function onUp() {
                handle.removeEventListener('pointermove', onMove);
                handle.removeEventListener('pointerup', onUp);
                if (!dragging) {
                    wrapper.classList.toggle('expanded');
                    handle.setAttribute('aria-expanded', wrapper.classList.contains('expanded'));
                    adjustPanelPosition(wrapper);
                } else {
                    wrapper._nkgAdjusted = false;
                    GM_setValue(POSITION_KEY, JSON.stringify({
                        left: wrapper.style.left,
                        top: wrapper.style.top
                    }));
                }
            }

            handle.addEventListener('pointermove', onMove);
            handle.addEventListener('pointerup', onUp);
        });
    }

    // 创建浮动尺寸切换面板(点击触发按钮展开/收起,可拖动)
    function createSizeToggle() {
        const wrapper = document.createElement('div');
        wrapper.id = 'nkg-size-toggle';

        // 头部:图标 + 标题同行
        const header = document.createElement('div');
        header.className = 'nkg-header';

        const trigger = document.createElement('img');
        trigger.id = 'nkg-trigger-btn';
        trigger.src = ICON_URL;
        trigger.alt = '9kgCoverPeek';
        trigger.setAttribute('role', 'button');
        trigger.setAttribute('aria-label', '切换封面图尺寸面板');
        trigger.setAttribute('aria-expanded', 'false');
        header.appendChild(trigger);

        const title = document.createElement('div');
        title.className = 'nkg-panel-title';
        title.textContent = 'Cover Peek 👀';
        header.appendChild(title);

        wrapper.appendChild(header);

        const panel = document.createElement('div');
        panel.id = 'nkg-size-panel';
        panel.setAttribute('role', 'group');
        panel.setAttribute('aria-label', '封面尺寸选项');
        const currentSize = getCurrentSize();

        // 封面尺寸选项行
        const row = document.createElement('div');
        row.className = 'nkg-option-row';
        const label = document.createElement('span');
        label.className = 'nkg-option-label';
        label.id = 'nkg-size-label';
        label.textContent = '封面尺寸:';
        row.appendChild(label);

        const btns = document.createElement('div');
        btns.className = 'nkg-option-btns';
        btns.setAttribute('role', 'radiogroup');
        btns.setAttribute('aria-labelledby', 'nkg-size-label');
        Object.entries(SIZE_CONFIG).forEach(([key, config]) => {
            const btn = document.createElement('button');
            btn.textContent = config.label;
            btn.dataset.size = key;
            btn.className = key === currentSize ? 'nkg-btn active' : 'nkg-btn';
            btn.setAttribute('role', 'radio');
            btn.setAttribute('aria-checked', key === currentSize);
            btn.addEventListener('click', () => updateAllImages(key));
            btns.appendChild(btn);
        });
        row.appendChild(btns);
        panel.appendChild(row);

        wrapper.appendChild(panel);
        document.body.appendChild(wrapper);

        // 恢复拖拽位置(校验视口边界)
        try {
            const pos = JSON.parse(GM_getValue(POSITION_KEY, 'null'));
            if (pos && pos.left && pos.top) {
                const left = parseInt(pos.left, 10);
                const top = parseInt(pos.top, 10);
                if (left >= 0 && left < window.innerWidth - 20 && top >= 0 && top < window.innerHeight - 20) {
                    wrapper.style.left = pos.left;
                    wrapper.style.top = pos.top;
                    wrapper.style.right = 'auto';
                    wrapper.style.bottom = 'auto';
                }
            }
        } catch (_) {}

        makeDraggable(wrapper, trigger);
    }

    // 注入样式
    function addStyles() {
        GM_addStyle(`
            /* 封面图容器(骨架屏) */
            .nkg-cover-wrapper {
                display: inline-block;
                vertical-align: middle;
                margin-right: 5px;
                min-width: 40px;
                min-height: 40px;
                border-radius: 4px;
                background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
                background-size: 200% 100%;
                animation: nkg-shimmer 1.5s ease infinite;
            }
            .nkg-cover-wrapper.nkg-loaded {
                min-width: unset;
                min-height: unset;
                background: none;
                animation: none;
            }
            .nkg-cover-wrapper.nkg-error {
                display: inline-flex;
                align-items: center;
                min-width: unset;
                min-height: unset;
                padding: 2px 6px;
                background: #f8f8f8;
                border: 1px dashed #ccc;
                font-size: 11px;
                color: #999;
                animation: none;
            }
            @keyframes nkg-shimmer {
                0% { background-position: 200% 0; }
                100% { background-position: -200% 0; }
            }
            /* 封面图 */
            .nkg-cover-img {
                max-width: var(--nkg-cover-size, 280px);
                vertical-align: middle;
                border-radius: 4px;
                opacity: 0;
                transition: max-width 0.3s ease, opacity 0.3s ease;
            }
            .nkg-loaded .nkg-cover-img {
                opacity: 1;
            }
            /* 浮动面板 */
            #nkg-size-toggle {
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 9999;
                padding: 6px;
                border-radius: 16px;
                background: transparent;
                box-shadow: none;
                overflow: hidden;
                max-width: 40px;
                max-height: 40px;
                transition: max-width 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
                            max-height 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
                            background 0.3s ease,
                            box-shadow 0.3s ease,
                            border-radius 0.3s ease;
            }
            #nkg-size-toggle.expanded {
                max-width: 400px;
                max-height: 200px;
                background: #fff;
                box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12);
            }
            #nkg-size-toggle.nkg-adjusting {
                transition: max-width 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
                            max-height 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
                            background 0.3s ease,
                            box-shadow 0.3s ease,
                            border-radius 0.3s ease,
                            left 0.35s cubic-bezier(0.25, 0.8, 0.25, 1),
                            top 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
            }
            #nkg-trigger-btn {
                width: 28px;
                height: 28px;
                min-width: 28px;
                border-radius: 50%;
                cursor: grab;
                transition: transform 0.2s;
                user-select: none;
                -webkit-user-drag: none;
            }
            #nkg-trigger-btn:hover {
                transform: scale(1.1);
            }
            .nkg-header {
                display: flex;
                align-items: center;
                gap: 8px;
                white-space: nowrap;
            }
            #nkg-size-panel {
                margin-top: 6px;
                padding: 4px 8px 2px;
                opacity: 0;
                transition: opacity 0.3s ease;
            }
            #nkg-size-toggle.expanded #nkg-size-panel {
                opacity: 1;
            }
            .nkg-panel-title {
                font-size: 16px;
                font-weight: 700;
                color: #222;
                white-space: nowrap;
                flex: 1;
                word-spacing: 0.4em;
                opacity: 0;
                transition: opacity 0.3s ease;
            }
            #nkg-size-toggle.expanded .nkg-panel-title {
                opacity: 1;
            }
            .nkg-option-row {
                display: flex;
                align-items: center;
                gap: 6px;
                white-space: nowrap;
            }
            .nkg-option-label {
                font-size: 13px;
                color: #333;
                font-weight: 500;
            }
            .nkg-option-btns {
                display: flex;
                gap: 4px;
            }
            .nkg-btn {
                border: 1px solid #e0e0e0;
                padding: 4px 14px;
                border-radius: 10px;
                cursor: pointer;
                background: #fafafa;
                color: #555;
                font-size: 13px;
                transition: color 0.2s, background 0.2s, border-color 0.2s, transform 0.15s;
            }
            .nkg-btn:hover {
                color: #333;
                background: #f0f0f0;
                border-color: #ccc;
            }
            .nkg-btn:active {
                transform: scale(0.92);
            }
            .nkg-btn.active {
                background: #4a6cf7;
                color: #fff;
                border-color: #4a6cf7;
            }
        `);
    }

    // 初始化
    function init() {
        addStyles();
        document.documentElement.style.setProperty('--nkg-cover-size', SIZE_CONFIG[getCurrentSize()].maxWidth);
        createSizeToggle();

        document.querySelectorAll('span[aria-describedby]').forEach(processSpan);

        new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.matches('span[aria-describedby]')) processSpan(node);
                        node.querySelectorAll('span[aria-describedby]').forEach(processSpan);
                    }
                }
            }
        }).observe(document.body, { childList: true, subtree: true });
    }

    if (document.readyState === 'complete') init();
    else window.addEventListener('load', init);
})();