Greasy Fork

Chzzk_L&V: Dal.wiki Viewer

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

当前为 2025-05-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         Chzzk_L&V: Dal.wiki Viewer
// @namespace    Chzzk_L&V: Dal.wiki Viewer
// @version      1.0.2
// @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, toggleBtn, iframe, closeBtn;
    let viewMode = 'agenda'; // 기본 보기 모드는 일간 (month or agenda)

    const fixedPosition = { top: 15, right: 315 };
    const iframeDefaultWidth = 1000;
    const iframeDefaultHeight = 700;

    function applyButtonPosition() {
        Object.assign(buttonContainer.style, {
            position: 'fixed',
            top: `${fixedPosition.top}px`,
            right: `${fixedPosition.right}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
        });
    }

    function updateToggleLabel() {
        // 버튼에 '월간 보기' 또는 '일간 보기' 명시
        toggleBtn.textContent = viewMode === 'month' ? '월(月)' : '일(日)';
    }

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

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

        // 2) 뷰 모드 토글 버튼 (크기 유지, 레이블 표시)
        toggleBtn = document.createElement('button');
        updateToggleLabel();
        Object.assign(toggleBtn.style, {
            padding:       '6px 8px',
            background:    '#555555',
            color:         'white',
            border:        'none',
            borderRadius:  '4px',
            cursor:        'pointer',
            fontSize:      '12px',
            lineHeight:    '1.2'
        });
        buttonContainer.appendChild(toggleBtn);

        // 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();
    }

    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();
    }

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

        // 뷰 모드 토글: iframe이 열린 상태에서만 적용
        toggleBtn.addEventListener('click', () => {
            viewMode = viewMode === 'month' ? 'agenda' : 'month';
            updateToggleLabel();
            if (iframe.style.display === 'block') {
                // 새로 열기
                openIframe();
            }
        });

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

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

    function onReady(fn) {
        if (document.body) return fn();
        new MutationObserver((obs) => {
            if (document.body) {
                obs.disconnect();
                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();
    });

})();