DLive Tools

DLive 화면 수정 및 렉 요소 최적화

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         DLive Tools
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  DLive 화면 수정 및 렉 요소 최적화
// @author       Dacchi
// @match        https://dlive.tv/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const defaultSettings = {
        hideTopNav: true,
        hideDonation: true,
        hideTopContributors: true,
        hideLike: true,
        emoteScale: 50,
        chatWidth: 350
    };

    let settings = { ...defaultSettings };
    try {
        const saved = localStorage.getItem('dlive-custom-settings');
        if (saved) settings = { ...settings, ...JSON.parse(saved) };
    } catch (e) {}

    const saveSettings = () => {
        localStorage.setItem('dlive-custom-settings', JSON.stringify(settings));
    };

    // CSS
    const injectCustomCSS = () => {
        if (document.getElementById('dlive-custom-style')) return;
        const style = document.createElement('style');
        style.id = 'dlive-custom-style';
        style.textContent = `
            .dplayer,.dplayer-video-wrap{width:100%!important;height:100%!important;max-height:100vh!important;flex-grow:1!important}
            .dplayer-video{width:100%!important;height:100%!important;max-height:100vh!important;object-fit:contain!important}
            #liveContainer{display:flex!important;height:100%!important;flex-grow:1!important}
            #liveContainer>div{flex-grow:1!important;max-width:100%!important;height:100%!important}
            .v-content__wrap>div>div{max-width:100%!important;height:100%!important}

            body.setting-hide-nav .dlive-top-nav-target{
                position:fixed!important;top:0!important;left:0!important;width:100%!important;height:56px!important;
                z-index:10000!important;transition:transform .3s ease-in-out,opacity .3s ease-in-out!important;
                transform:translateY(-100%)!important;opacity:0!important;background-color:rgb(15,18,20)!important}
            body.setting-hide-nav #livestream-info{
                position:fixed!important;top:56px!important;left:0!important;width:100%!important;z-index:9999!important;
                transition:transform .3s ease-in-out,opacity .3s ease-in-out!important;
                transform:translateY(calc(-100% - 56px))!important;opacity:0!important;
                background-color:rgba(18,18,18,.95)!important;padding:10px!important;box-sizing:border-box!important}
            body.setting-hide-nav .dlive-top-nav-target.show-element,
            body.setting-hide-nav #livestream-info.show-element{transform:translateY(0)!important;opacity:1!important}

            body.setting-hide-like .like-button{display:none!important}
            body.setting-hide-donation .donation-wrapper{display:none!important}
            body.setting-hide-contributors .top-contributors,
            body.setting-hide-contributors .chatroom-header{display:none!important}

            .emote-img{
                max-width:calc(80px*(var(--dlive-emote-scale)/100))!important;
                max-height:calc(80px*(var(--dlive-emote-scale)/100))!important;
                height:auto!important;width:auto!important}

            .dlive-resizer{width:8px;cursor:ew-resize;background-color:transparent;z-index:1000;position:relative;flex-shrink:0;transition:background-color .2s}
            .dlive-resizer:hover,.dlive-resizer.active{background-color:rgba(255,204,0,.5)}
            body.is-resizing{user-select:none!important;cursor:ew-resize!important}
            body.is-resizing iframe,body.is-resizing video{pointer-events:none!important}

            .dlive-settings-wrapper{position:fixed;top:28px;left:50%;transform:translate(-50%,-50%);z-index:10001;font-family:sans-serif;display:flex;flex-direction:column;align-items:center}
            .dlive-settings-btn{width:36px;height:36px;background-color:#1a1c1f;color:#ffcc00;border:1px solid #333;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 5px rgba(0,0,0,.5);transition:all .2s;font-size:18px;line-height:1}
            .dlive-settings-btn:hover{background-color:#2a2c2f;transform:scale(1.05)}
            .dlive-settings-panel{position:absolute;top:46px;left:50%;transform:translateX(-50%);width:280px;background-color:#1a1c1f;color:#e0e0e0;border:1px solid #333;border-radius:12px;padding:16px;display:none;box-shadow:0 8px 24px rgba(0,0,0,.8)}
            .dlive-settings-panel.open{display:block}
            .dlive-settings-header{font-size:14px;font-weight:bold;margin-bottom:12px;color:#fff;border-bottom:1px solid #333;padding-bottom:8px}
            .dlive-setting-item{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;font-size:13px}
            .dlive-setting-slider-wrap{margin-bottom:15px}
            .dlive-setting-slider-wrap label{display:flex;justify-content:space-between;font-size:13px;margin-bottom:8px}

            .dlive-switch{position:relative;display:inline-block;width:34px;height:20px}
            .dlive-switch input{opacity:0;width:0;height:0}
            .dlive-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#444;transition:.4s;border-radius:20px}
            .dlive-slider:before{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:white;transition:.4s;border-radius:50%}
            input:checked+.dlive-slider{background-color:#ffcc00}
            input:checked+.dlive-slider:before{transform:translateX(14px)}

            input[type=range].dlive-range{-webkit-appearance:none;appearance:none;width:100%;height:6px;background:#555;border-radius:3px;outline:none;margin:0}
            input[type=range].dlive-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;height:16px;width:16px;border-radius:50%;background:#ffcc00;cursor:pointer;margin-top:-5px}
            input[type=range].dlive-range::-webkit-slider-runnable-track{width:100%;height:6px;cursor:pointer;background:#555;border-radius:3px}
            #dlive-hover-trigger {position:fixed;top:0;left:0;width:100%;height:30px;z-index:9998;background:transparent;}
        `;
        document.head.appendChild(style);
    };

    const applySettings = () => {
        const cl = document.body.classList;
        cl.toggle('setting-hide-nav',          !!settings.hideTopNav);
        cl.toggle('setting-hide-like',         !!settings.hideLike);
        cl.toggle('setting-hide-donation',     !!settings.hideDonation);
        cl.toggle('setting-hide-contributors', !!settings.hideTopContributors);
        document.documentElement.style.setProperty('--dlive-emote-scale', settings.emoteScale);
        applyChatWidth(settings.chatWidth);
    };

    let _chatPanel = null;
    const getChatPanel = () => {
        if (_chatPanel && document.contains(_chatPanel)) return _chatPanel;
        const chatBody = document.querySelector('.chatbody');
        if (!chatBody) return null;
        let el = chatBody;
        while (el?.parentElement) {
            if (window.getComputedStyle(el.parentElement).display === 'flex' &&
                window.getComputedStyle(el.parentElement).flexDirection === 'row') break;
            el = el.parentElement;
        }
        _chatPanel = el ?? null;
        return _chatPanel;
    };

    const applyChatWidth = (width) => {
        const panel = getChatPanel();
        if (!panel) return;
        const w = width + 'px';
        
        panel.style.width = w;
        panel.style.minWidth = w;
        panel.style.maxWidth = w;
        panel.style.flex = 'none';
    };

    // 설정 UI
    const injectSettingsUI = () => {
        if (document.getElementById('dlive-settings-wrapper')) return true;
        
        const topNav = document.querySelector('.dlive-top-nav-target');
        if (!topNav) return false;

        const wrapper = document.createElement('div');
        wrapper.id = 'dlive-settings-wrapper';
        wrapper.className = 'dlive-settings-wrapper';
        wrapper.innerHTML = `
            <div class="dlive-settings-btn" id="dlive-settings-btn">⚙️</div>
            <div class="dlive-settings-panel" id="dlive-settings-panel">
                <div class="dlive-settings-header">UI 설정</div>
                <div class="dlive-setting-item"><span>상단바 자동 숨김</span>
                    <label class="dlive-switch"><input type="checkbox" id="toggle-topnav" ${settings.hideTopNav?'checked':''}><span class="dlive-slider"></span></label></div>
                <div class="dlive-setting-item"><span>보물상자 숨기기</span>
                    <label class="dlive-switch"><input type="checkbox" id="toggle-donation" ${settings.hideDonation?'checked':''}><span class="dlive-slider"></span></label></div>
                <div class="dlive-setting-item"><span>기부자 숨기기</span>
                    <label class="dlive-switch"><input type="checkbox" id="toggle-contributors" ${settings.hideTopContributors?'checked':''}><span class="dlive-slider"></span></label></div>
                <div class="dlive-setting-item"><span>하트 숨기기</span>
                    <label class="dlive-switch"><input type="checkbox" id="toggle-like" ${settings.hideLike?'checked':''}><span class="dlive-slider"></span></label></div>
                <div class="dlive-setting-slider-wrap">
                    <label><span>GIF 채팅 크기</span><span id="scale-display">${settings.emoteScale}%</span></label>
                    <input type="range" id="slider-emote" class="dlive-range" min="10" max="100" value="${settings.emoteScale}">
                </div>
                <div class="dlive-setting-slider-wrap">
                    <label><span>채팅창 가로 크기</span><span id="width-display">${settings.chatWidth}px</span></label>
                    <input type="range" id="slider-chatwidth" class="dlive-range" min="200" max="800" value="${settings.chatWidth}">
                </div>
            </div>`;
        topNav.appendChild(wrapper);

        document.getElementById('dlive-settings-btn').addEventListener('click', () => {
            document.getElementById('dlive-settings-panel').classList.toggle('open');
        });

        const bindToggle = (id, key) => {
            document.getElementById(id)?.addEventListener('change', e => {
                settings[key] = e.target.checked;
                applySettings();
                saveSettings();
            });
        };
        bindToggle('toggle-topnav',        'hideTopNav');
        bindToggle('toggle-donation',      'hideDonation');
        bindToggle('toggle-contributors',  'hideTopContributors');
        bindToggle('toggle-like',          'hideLike');

        const emoteSlider   = document.getElementById('slider-emote');
        const scaleDisplay  = document.getElementById('scale-display');
        const widthSlider   = document.getElementById('slider-chatwidth');
        const widthDisplay  = document.getElementById('width-display');

        emoteSlider?.addEventListener('input', e => {
            scaleDisplay.textContent = e.target.value + '%';
            settings.emoteScale = +e.target.value;
            document.documentElement.style.setProperty('--dlive-emote-scale', settings.emoteScale);
        });
        emoteSlider?.addEventListener('change', saveSettings);

        widthSlider?.addEventListener('input', e => {
            widthDisplay.textContent = e.target.value + 'px';
            settings.chatWidth = +e.target.value;
            applyChatWidth(settings.chatWidth);
        });
        widthSlider?.addEventListener('change', saveSettings);
        
        return true;
    };

    let _navTagged = false;
    const tagElements = () => {
        if (_navTagged) return true;
        const navMenu = document.querySelector('.nav-bar-menu');
        if (!navMenu) return false;
        
        let topNav = navMenu.closest('header,nav,[class*="header"],[id*="nav"]') ?? navMenu;
        topNav.classList.add('dlive-top-nav-target');
        navMenu.dataset.tagged = 'true';
        _navTagged = true;
        return true;
    };

    let _hoverSetupDone = false;
    const setupHoverEffect = () => {
        if (_hoverSetupDone) return true;
        
        if (!document.getElementById('dlive-hover-trigger')) {
            const trigger = document.createElement('div');
            trigger.id = 'dlive-hover-trigger';
            document.body.appendChild(trigger);
        }

        let navShown = false;
        let hideTimeout = null;

        const toggleNav = (show) => {
            if (show === navShown) return;
            navShown = show;
            document.querySelector('.dlive-top-nav-target')?.classList.toggle('show-element', show);
            document.getElementById('livestream-info')?.classList.toggle('show-element', show);
        };

        document.addEventListener('mouseover', e => {
            if (!settings.hideTopNav) return;
            
            const isHoveringNav = e.target.closest('#livestream-info, .dlive-top-nav-target, #dlive-settings-wrapper, #dlive-hover-trigger');
            
            if (isHoveringNav) {
                if (hideTimeout) clearTimeout(hideTimeout);
                toggleNav(true);
            }
        });

        document.addEventListener('mouseout', e => {
            if (!settings.hideTopNav) return;
            
            const isLeavingNav = !e.relatedTarget || !e.relatedTarget.closest('#livestream-info, .dlive-top-nav-target, #dlive-settings-wrapper, #dlive-hover-trigger');
            
            if (isLeavingNav) {
                if (hideTimeout) clearTimeout(hideTimeout);
                hideTimeout = setTimeout(() => {
                    toggleNav(false);
                }, 100);
            }
        });

        _hoverSetupDone = true;
        return true;
    };

    const setupResizer = () => {
        if (document.querySelector('.dlive-resizer')) return true;
        const chatPanel = getChatPanel();
        if (!chatPanel?.parentElement) return false;

        const container = chatPanel.parentElement;
        const resizer = document.createElement('div');
        resizer.className = 'dlive-resizer';
        container.insertBefore(resizer, chatPanel);

        let rafId = null;

        const onMouseMove = (e) => {
            if (rafId) return;
            rafId = requestAnimationFrame(() => {
                rafId = null;
                const rect = container.getBoundingClientRect();
                const w = Math.round(rect.right - e.clientX);
                if (w < 200 || w > 800) return;

                applyChatWidth(w);
                settings.chatWidth = w;

                const slider  = document.getElementById('slider-chatwidth');
                const display = document.getElementById('width-display');
                if (slider)  slider.value = w;
                if (display) display.textContent = w + 'px';
            });
        };

        const onMouseUp = () => {
            document.querySelector('.dlive-resizer')?.classList.remove('active');
            document.body.classList.remove('is-resizing');
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            saveSettings();
        };

        resizer.addEventListener('mousedown', e => {
            resizer.classList.add('active');
            document.body.classList.add('is-resizing');
            e.preventDefault();
            
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });
        
        return true;
    };

    const runInitializer = () => {
        let attempts = 0;
        
        setupHoverEffect();

        const initInterval = setInterval(() => {
            attempts++;
            
            const isTagged = tagElements();
            const isSettingsInjected = injectSettingsUI();
            const isResizerSetup = setupResizer();
            
            applySettings();

            if ((isTagged && isSettingsInjected && isResizerSetup) || attempts > 30) {
                clearInterval(initInterval);
            }
        }, 500);
    };

    const autoConfirmAge = () => {
        const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

        const tryConfirm = () => {
            const checkboxes = document.querySelectorAll('.agree-check input[type="checkbox"]');
            if (checkboxes.length < 2) return false;

            const [cb1, cb2] = checkboxes;
            if (!cb1.checked) cb1.click();

            setTimeout(() => {
                if (!cb2.checked) cb2.click();

                setTimeout(() => {
                    const agreeBtn = document.querySelector('.btn.agree');
                    if (agreeBtn) agreeBtn.click();
                }, rand(350, 450));
            }, rand(350, 450));

            return true;
        };

        if (!tryConfirm()) {
            let attempts = 0;
            const checkInterval = setInterval(() => {
                attempts++;
                if (tryConfirm() || attempts > 15) {
                    clearInterval(checkInterval);
                }
            }, 1000);
        }
    };

    const init = () => {
        injectCustomCSS();
        runInitializer();
        autoConfirmAge();
    };

    document.readyState === 'loading'
        ? document.addEventListener('DOMContentLoaded', init)
        : init();
})();