Greasy Fork is available in English.

Chzzk_L&V: Dal.wiki Viewer

치지직 합방 일정과 대회/이벤트를 확인가능한 Dal.wiki를 치지직 방송을 보면서도 확인하고자 만들었습니다.

Bu scripti kur?
Yazarın tavsiye ettiği betik

Siz bunuda beğenebilirsiniz: Chzzk_L&V: Chatting Plus.

Bu scripti kur
// ==UserScript==
// @name         Chzzk_L&V: Dal.wiki Viewer
// @namespace    Chzzk_L&V: Dal.wiki Viewer
// @version      1.0.5
// @description  치지직 합방 일정과 대회/이벤트를 확인가능한 Dal.wiki를 치지직 방송을 보면서도 확인하고자 만들었습니다.
// @author       DOGJIP
// @match        *://chzzk.naver.com/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chzzk.naver.com
// ==/UserScript==

(function () {
    'use strict';

    // 치지직 도메인이 아니거나, iframe 내부라면 스크립트를 실행하지 않음
    if (!location.hostname.includes('chzzk.naver.com')) return;
    if (window.top !== window.self) return;

    const topicPath = '/topic/%EC%B9%98%EC%A7%80%EC%A7%81%20%ED%95%A9%EB%B0%A9%2F%EB%8C%80%ED%9A%8C%2F%EC%BD%98%ED%85%90%EC%B8%A0%20%EC%9D%BC%EC%A0%95';
    let buttonContainer, calendarBtn, streamerBtn, dayBtn, monthBtn, iframe, closeBtn, opacitySlider, rememberOpacityCheckbox, checkbox;
    let viewMode = 'agenda'; // 기본 보기 모드는 일간 (month or agenda)
    let currentOpacity = 1.0;

    const STORAGE_KEY = 'dalwiki_opacity';
    const fixedPosition = { top: 16, left: 150 };
    const iframeDefaultWidth = 1000;
    const iframeDefaultHeight = 800;

    function applyButtonPosition() {
        Object.assign(buttonContainer.style, {
            position: 'fixed',
            top: `${fixedPosition.top}px`,
            left: `${fixedPosition.left}px`,
            zIndex: 2147483647,
            display: 'flex',
            gap: '4px'
        });
    }

    function centerIframe() {
        const maxW = window.innerWidth * 0.9;
        const maxH = window.innerHeight * 0.9;
        const w = Math.min(iframeDefaultWidth, maxW);
        const h = Math.min(iframeDefaultHeight, maxH);
        const top = (window.innerHeight - h) / 2;
        const left = (window.innerWidth - w) / 2;

        Object.assign(iframe.style, {
            width:  w + 'px',
            height: h + 'px',
            top:    top + 'px',
            left:   left + 'px',
            display:   'block',
            position:  'fixed',
            border:    '2px solid #ccc',
            borderRadius: '8px',
            boxShadow: '0 0 12px rgba(0,0,0,0.4)',
            background: 'white',
            zIndex:     2147483646
        });

        Object.assign(closeBtn.style, {
            position:  'fixed',
            top:       (top - 30) + 'px',
            left:      (left + w - 30) + 'px',
            display:   'block',
            background:'crimson',
            color:     'white',
            border:    'none',
            padding:   '4px 8px',
            borderRadius: '50%',
            cursor:    'pointer',
            fontSize:  '12px',
            zIndex:    2147483647
        });
        // 1) 슬라이더 위치: closeBtn 왼쪽 (기존 동일)
        const sliderRight = parseFloat(closeBtn.style.left) - 110;
        opacitySlider.style.top  = closeBtn.style.top;
        opacitySlider.style.left = `${sliderRight}px`;

        // 2) 체크박스 위치: 슬라이더 왼쪽 (슬라이더 너비 100px + 여백 8px)
        const checkboxLeft = sliderRight - 100 - 8;
        rememberOpacityCheckbox.style.top  = closeBtn.style.top;
        rememberOpacityCheckbox.style.left = `${checkboxLeft}px`;

        streamerBtn.style.top     = rememberOpacityCheckbox.style.top;
        streamerBtn.style.left    = iframe.style.left;
        streamerBtn.style.display = 'block';

        // 일(日) 버튼: streamerBtn 폭 + 8px 여백 만큼 띄우기
        dayBtn.style.top     = rememberOpacityCheckbox.style.top;
        const streamerWidth  = streamerBtn.offsetWidth;
        const gap            = 8; // 원하는 여백(px)
        dayBtn.style.left    = `${parseFloat(iframe.style.left) + streamerWidth + gap}px`;
        dayBtn.style.display = 'block';

        // 월(月) 버튼
        monthBtn.style.top     = rememberOpacityCheckbox.style.top;
        // 일 버튼 바로 오른쪽으로 위치 (4px 여백)
        const dayWidth        = dayBtn.offsetWidth;
        const smallGap        = 4;
        monthBtn.style.left    = `${parseFloat(dayBtn.style.left) + dayWidth + smallGap}px`;
        monthBtn.style.display = 'block';
    }

    function createUI() {
        buttonContainer = document.createElement('div');
        document.body.appendChild(buttonContainer);

        // 스트리머 일정 버튼
        streamerBtn = document.createElement('button');
        streamerBtn.textContent = '🎥 스트리머 일정';
        Object.assign(streamerBtn.style, {
            display:      'none',
            position:     'fixed',
            zIndex:       2147483647,
            padding:      '4px 8px',
            fontSize:     '12px',
            background:   '#444',
            color:        'white',
            border:       'none',
            borderRadius: '4px',
            cursor:       'pointer'
        });
        document.body.appendChild(streamerBtn);

        // 1) 일정 보기 버튼
        calendarBtn = document.createElement('button');
        calendarBtn.textContent = '📅 일정 보기';
        Object.assign(calendarBtn.style, {
            padding:       '6px 8px',
            zIndex:       2147483647,
            background:    '#333333',
            color:         'white',
            border:        'none',
            borderRadius:  '6px',
            cursor:        'pointer',
            fontSize:      '12px'
        });
        buttonContainer.appendChild(calendarBtn);

        // 2-1) 일(日) 버튼
        dayBtn = document.createElement('button');
        dayBtn.textContent = '일(日)';
        Object.assign(dayBtn.style, {
            display:      'none',
            position:     'fixed',
            zIndex:       2147483647,
            padding:      '4px 8px',
            background:   '#555555',
            color:        'white',
            border:       'none',
            borderRadius: '4px',
            cursor:       'pointer',
            fontSize:     '12px',
            marginLeft:   '4px'
        });
        document.body.appendChild(dayBtn);

        // 2-2) 월(月) 버튼
        monthBtn = document.createElement('button');
        monthBtn.textContent = '월(月)';
        Object.assign(monthBtn.style, {
            display:      'none',
            position:     'fixed',
            zIndex:       2147483647,
            padding:      '4px 8px',
            background:   '#555555',
            color:        'white',
            border:       'none',
            borderRadius: '4px',
            cursor:       'pointer',
            fontSize:     '12px',
            marginLeft:   '4px'
        });
        document.body.appendChild(monthBtn);

        // 3) iframe
        iframe = document.createElement('iframe');
        Object.assign(iframe.style, { display: 'none' });
        document.body.appendChild(iframe);

        // 4) 닫기 버튼
        closeBtn = document.createElement('button');
        closeBtn.textContent = '✖';
        Object.assign(closeBtn.style, { display: 'none' });
        document.body.appendChild(closeBtn);

        applyButtonPosition();

        // 5) 투명도 조절 슬라이더
        opacitySlider = document.createElement('input');
        opacitySlider.type = 'range';
        opacitySlider.min = '0.3';
        opacitySlider.max = '1';
        opacitySlider.step = '0.01';
        opacitySlider.value = '1';
        Object.assign(opacitySlider.style, {
            display: 'none',
            position: 'fixed',
            zIndex: 2147483647,
            width: '100px',
            cursor: 'pointer'
        });
        document.body.appendChild(opacitySlider);

        // 6) 투명도 기억 여부 체크박스
        rememberOpacityCheckbox = document.createElement('label');
        rememberOpacityCheckbox.style.position = 'fixed';
        rememberOpacityCheckbox.style.zIndex = 2147483647;
        rememberOpacityCheckbox.style.top = '50px';
        rememberOpacityCheckbox.style.transform = 'translateX(0)';
        rememberOpacityCheckbox.style.fontSize = '12px';
        rememberOpacityCheckbox.style.color = '#fff';
        rememberOpacityCheckbox.style.background = 'rgba(34, 34, 34, 0.5)'; // 50% 투명한 검정색
        rememberOpacityCheckbox.style.padding = '4px 6px';
        rememberOpacityCheckbox.style.borderRadius = '4px';

        checkbox = document.createElement('input'); // <-- 전역 변수 사용
        checkbox.type = 'checkbox';
        checkbox.style.marginRight = '4px';
        rememberOpacityCheckbox.appendChild(checkbox);
        rememberOpacityCheckbox.appendChild(document.createTextNode('투명도 기억'));

        document.body.appendChild(rememberOpacityCheckbox);
        rememberOpacityCheckbox.style.display = 'none';
    }

    function openIframe() {
        const dt = new Date();
        const yyyy = dt.getFullYear();
        const mm = String(dt.getMonth()+1).padStart(2,'0');
        const dd = String(dt.getDate()).padStart(2,'0');
        iframe.src = `https://dal.wiki${topicPath}/${viewMode}?date=${yyyy}-${mm}-${dd}`;
        centerIframe();

        // 투명도 적용
        iframe.style.opacity = currentOpacity;
        opacitySlider.value = String(currentOpacity);
        opacitySlider.style.display = 'block';
        rememberOpacityCheckbox.style.display = 'block';
        checkbox.checked = !!localStorage.getItem(STORAGE_KEY);
    }

    function bindEvents() {
        // 일정 보기
        calendarBtn.addEventListener('click', () => {
            if (iframe.style.display === 'block') {
                iframe.style.display = 'none';
                closeBtn.style.display = 'none';
                opacitySlider.style.display = 'none';
                rememberOpacityCheckbox.style.display = 'none';
                streamerBtn.style.display = dayBtn.style.display = monthBtn.style.display = 'none';
            } else {
                openIframe();
            }
        });

        // 닫기 버튼
        closeBtn.addEventListener('click', () => {
            iframe.style.display = 'none';
            closeBtn.style.display = 'none';
            opacitySlider.style.display = 'none';
            rememberOpacityCheckbox.style.display = 'none';
            streamerBtn.style.display = dayBtn.style.display = monthBtn.style.display = 'none';
        });

        // 화면 리사이즈 대응
        window.addEventListener('resize', () => {
            if (iframe.style.display === 'block') centerIframe();
        });

        // 슬라이더 변경 시, 저장 옵션이 체크되어 있으면 localStorage에 저장
        opacitySlider.addEventListener('input', () => {
            currentOpacity = parseFloat(opacitySlider.value);
            iframe.style.opacity = currentOpacity;

            if (checkbox.checked) {
                localStorage.setItem(STORAGE_KEY, currentOpacity.toString());
            }
        });

        // 체크박스 토글: 체크 해제 시 저장값 제거
        checkbox.addEventListener('change', () => {
            if (!checkbox.checked) {
                localStorage.removeItem(STORAGE_KEY);
            } else {
                localStorage.setItem(STORAGE_KEY, currentOpacity.toString());
            }
        });

        streamerBtn.addEventListener('click', () => {
            const dt  = new Date();
            const yyyy= dt.getFullYear();
            const mm  = String(dt.getMonth()+1).padStart(2,'0');
            const dd  = String(dt.getDate()).padStart(2,'0');
            iframe.src = `https://dal.wiki/topic/%EC%B9%98%EC%A7%80%EC%A7%81%20%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%A8%B8%20%EC%9D%BC%EC%A0%95/agenda?date=${yyyy}-${mm}-${dd}`;
        });

            // 日 버튼 클릭 시 일간(agenda) 보기
        dayBtn.addEventListener('click', () => {
            const dt = new Date();
            const yyyy = dt.getFullYear();
            const mm = String(dt.getMonth()+1).padStart(2,'0');
            const dd = String(dt.getDate()).padStart(2,'0');
            iframe.src = `https://dal.wiki${topicPath}/agenda?date=${yyyy}-${mm}-${dd}`;
        });

        // 月 버튼 클릭 시 월간(month) 보기
        monthBtn.addEventListener('click', () => {
            const dt = new Date();
            const yyyy = dt.getFullYear();
            const mm = String(dt.getMonth()+1).padStart(2,'0');
            const dd = String(dt.getDate()).padStart(2,'0');
            iframe.src = `https://dal.wiki${topicPath}/month?date=${yyyy}-${mm}-${dd}`;
        });
    }

    function onReady(fn) {
        if (document.body) {
            // 초기 투명도 로드
            const saved = localStorage.getItem(STORAGE_KEY);
            if (saved) {
                currentOpacity = parseFloat(saved);
            }
            return fn();
        }

        new MutationObserver((obs) => {
            if (document.body) {
                obs.disconnect();

                const saved = localStorage.getItem(STORAGE_KEY);
                if (saved) {
                    currentOpacity = parseFloat(saved);
                }

                fn();
            }
        }).observe(document.documentElement, { childList: true });
    }

        function observeBodyStyle() {
            const body = document.body;
            const observer = new MutationObserver(() => {
                const style = window.getComputedStyle(body);
                const wide = style.overflow === 'hidden' && style.position === 'fixed';
                buttonContainer.style.display = wide ? 'none' : 'flex';
            });
            observer.observe(body, { attributes: true, attributeFilter: ['style'] });

            // 초기 상태 설정
            const initStyle = window.getComputedStyle(body);
            const isWide = initStyle.overflow === 'hidden' && initStyle.position === 'fixed';
            buttonContainer.style.display = isWide ? 'none' : 'flex';
        }

    onReady(() => {
        createUI();
        bindEvents();
        observeBodyStyle();
    });

})();