您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
치지직에서 Dal.wiki 일정 확인 및 추가기능 + WWME 뷰어 기능 추가 (기본 시작페이지 WWME)
// ==UserScript== // @name Chzzk_L&V: Dal.wiki & WWME Viewer // @namespace Chzzk_L&V: Third Party Iframe Viewer // @version 1.0.8.1 // @description 치지직에서 Dal.wiki 일정 확인 및 추가기능 + WWME 뷰어 기능 추가 (기본 시작페이지 WWME) // @author DOGJIP // @match *://chzzk.naver.com/* // @match https://dal.wiki/* // @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'; const isChzzk = location.hostname.includes('chzzk.naver.com'); const isDalWiki = location.hostname.includes('dal.wiki'); // ========================= CHZZK: 일정 뷰어 ========================= if (isChzzk && window.top === window.self && !location.pathname.includes('/chat')) { let buttonContainer, MainBtn, streamerBtn, dayBtn, wwmeBtn; let iframe, closeBtn, opacitySlider, rememberOpacityCheckbox, checkbox; let viewMode = 'wwme'; // 'streamer' | 'daily' | 'wwme' 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 }); const sliderRight = parseFloat(closeBtn.style.left) - 110; opacitySlider.style.top = closeBtn.style.top; opacitySlider.style.left = `${sliderRight}px`; const checkboxLeft = sliderRight - 100 - 8; rememberOpacityCheckbox.style.top = closeBtn.style.top; rememberOpacityCheckbox.style.left = `${checkboxLeft}px`; streamerBtn.style.top = closeBtn.style.top; streamerBtn.style.left = iframe.style.left; streamerBtn.style.display = 'block'; dayBtn.style.top = closeBtn.style.top; const streamerWidth = streamerBtn.offsetWidth; dayBtn.style.left = `${parseFloat(iframe.style.left) + streamerWidth + 8}px`; dayBtn.style.display = 'block'; wwmeBtn.style.top = closeBtn.style.top; wwmeBtn.style.left = `${parseFloat(dayBtn.style.left) + dayBtn.offsetWidth + 4}px`; wwmeBtn.style.display = 'block'; } function updateMainBtnText(mode) { switch (mode) { case 'streamer': MainBtn.textContent = '🎥 스트리머 일정'; break; case 'daily': MainBtn.textContent = '📅 치지직 일간 일정'; break; case 'wwme': MainBtn.textContent = '🌐 WWME'; break; } } function createUI() { buttonContainer = document.createElement('div'); document.body.appendChild(buttonContainer); MainBtn = document.createElement('button'); Object.assign(MainBtn.style, { padding: '6px 8px', zIndex: 2147483647, background: '#333', color: 'white', borderRadius: '6px', border: 'none', fontSize: '12px' }); buttonContainer.appendChild(MainBtn); streamerBtn = document.createElement('button'); streamerBtn.textContent = '🎥 스트리머 일정'; dayBtn = document.createElement('button'); [streamerBtn, dayBtn].forEach(btn => { btn.style.display = 'none'; btn.style.position = 'fixed'; btn.style.zIndex = '2147483647'; btn.style.padding = '4px 8px'; btn.style.background = '#555'; btn.style.color = 'white'; btn.style.borderRadius = '4px'; btn.style.border = 'none'; btn.style.cursor = 'pointer'; btn.style.fontSize = '12px'; document.body.appendChild(btn); }); dayBtn.textContent = '📅 치지직 일간 일정'; iframe = document.createElement('iframe'); Object.assign(iframe.style, { display: 'none' }); document.body.appendChild(iframe); closeBtn = document.createElement('button'); closeBtn.textContent = '✖'; Object.assign(closeBtn.style, { display: 'none' }); document.body.appendChild(closeBtn); 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' }); document.body.appendChild(opacitySlider); rememberOpacityCheckbox = document.createElement('label'); checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.marginRight = '4px'; rememberOpacityCheckbox.appendChild(checkbox); rememberOpacityCheckbox.appendChild(document.createTextNode('투명도 기억')); Object.assign(rememberOpacityCheckbox.style, { display: 'none', position: 'fixed', zIndex: 2147483647, fontSize: '12px', color: '#fff', background: 'rgba(34,34,34,0.5)', padding: '4px 6px', borderRadius: '4px' }); document.body.appendChild(rememberOpacityCheckbox); // WWME 버튼 생성 wwmeBtn = document.createElement('button'); wwmeBtn.textContent = '🌐 WWME'; Object.assign(wwmeBtn.style, { display: 'none', position: 'fixed', zIndex: '2147483647', padding: '4px 8px', background: '#555', color: 'white', borderRadius: '4px', border: 'none', cursor: 'pointer', fontSize: '12px' }); document.body.appendChild(wwmeBtn); applyButtonPosition(); } function openIframe(mode = viewMode) { if (mode === 'wwme') { // WWME 모드: 사전 로딩된 iframe이 아니더라도 URL 설정 iframe.src = 'https://wwme.kr/'; centerIframe(); // 투명도 기억 적용 iframe.style.opacity = currentOpacity; opacitySlider.value = currentOpacity.toString(); checkbox.checked = !!localStorage.getItem(STORAGE_KEY); // 표시 iframe.style.display = 'block'; closeBtn.style.display = 'block'; opacitySlider.style.display = 'block'; rememberOpacityCheckbox.style.display = 'block'; // 버튼 위치 streamerBtn.style.top = closeBtn.style.top; streamerBtn.style.left = iframe.style.left; dayBtn.style.top = closeBtn.style.top; dayBtn.style.left = `${parseFloat(streamerBtn.style.left) + streamerBtn.offsetWidth + 8}px`; wwmeBtn.style.top = closeBtn.style.top; wwmeBtn.style.left = `${parseFloat(dayBtn.style.left) + dayBtn.offsetWidth + 4}px`; return; } // Dal.wiki 모드: daily 또는 streamer 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'); let topicPath = ''; let page = ''; switch (mode) { case 'daily': 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'; page = 'agenda'; break; case 'streamer': topicPath = '/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'; page = 'agenda'; break; default: return; } iframe.src = `https://dal.wiki${topicPath}/${page}?date=${yyyy}-${mm}-${dd}`; centerIframe(); // 투명도 기억 적용 iframe.style.opacity = currentOpacity; opacitySlider.value = currentOpacity.toString(); checkbox.checked = !!localStorage.getItem(STORAGE_KEY); // 표시 iframe.style.display = 'block'; closeBtn.style.display = 'block'; opacitySlider.style.display = 'block'; rememberOpacityCheckbox.style.display = 'block'; } function bindEvents() { MainBtn.onclick = () => { if (iframe.style.display === 'block') { iframe.style.display = closeBtn.style.display = opacitySlider.style.display = rememberOpacityCheckbox.style.display = 'none'; streamerBtn.style.display = dayBtn.style.display = wwmeBtn.style.display = 'none'; } else { openIframe(); } }; closeBtn.onclick = () => { iframe.style.display = closeBtn.style.display = opacitySlider.style.display = rememberOpacityCheckbox.style.display = 'none'; streamerBtn.style.display = dayBtn.style.display = wwmeBtn.style.display = 'none'; }; window.addEventListener('resize', () => iframe.style.display === 'block' && centerIframe()); opacitySlider.oninput = () => { currentOpacity = parseFloat(opacitySlider.value); iframe.style.opacity = currentOpacity; if (checkbox.checked) localStorage.setItem(STORAGE_KEY, currentOpacity.toString()); }; checkbox.onchange = () => { if (checkbox.checked) localStorage.setItem(STORAGE_KEY, currentOpacity.toString()); else localStorage.removeItem(STORAGE_KEY); }; streamerBtn.onclick = () => { viewMode = 'streamer'; openIframe(viewMode); }; dayBtn.onclick = () => { viewMode = 'daily'; openIframe(viewMode); }; wwmeBtn.onclick = () => { viewMode = 'wwme'; openIframe(viewMode); }; } function observeBodyStyle() { const observer = new MutationObserver(() => { const style = window.getComputedStyle(document.body); const hidden = style.overflow === 'hidden' && style.position === 'fixed'; buttonContainer.style.display = hidden ? 'none' : 'flex'; }); observer.observe(document.body, { attributes: true, attributeFilter: ['style'] }); } function init() { const saved = localStorage.getItem(STORAGE_KEY); if (saved) currentOpacity = parseFloat(saved); createUI(); updateMainBtnText(viewMode); bindEvents(); observeBodyStyle(); } if (document.body) init(); else new MutationObserver((obs) => document.body && (obs.disconnect(), init())).observe(document.documentElement, { childList: true }); } // ========================= DAL.WIKI 내 고정 레이아웃 ========================= if (isDalWiki) { const observer = new MutationObserver(() => { const aside = document.querySelector('aside.md\\:w-\\[176px\\]'); if (!aside || aside.dataset.stickyApplied === 'true') return; const addButtonAnchor = aside.querySelector('a[href*=\"/editor\"]'); const scrollWrapper = aside.querySelector('[style*=\"--radix-scroll-area-corner-width\"]'); const viewport = scrollWrapper?.querySelector('[data-radix-scroll-area-viewport]'); if (!addButtonAnchor || !scrollWrapper || !viewport) return; aside.dataset.stickyApplied = 'true'; viewport.style.minHeight = viewport.offsetHeight + 'px'; scrollWrapper.style.maxHeight = '50vh'; scrollWrapper.style.overflowY = 'auto'; const stickyContainer = document.createElement('div'); stickyContainer.style.position = 'sticky'; stickyContainer.style.top = '0'; stickyContainer.style.background = 'white'; stickyContainer.style.zIndex = '50'; stickyContainer.style.paddingBottom = '12px'; const config = { attributes: true, childList: false, subtree: false }; observer.disconnect(); stickyContainer.appendChild(addButtonAnchor); stickyContainer.appendChild(scrollWrapper); aside.insertBefore(stickyContainer, aside.firstChild); observer.observe(document.body, config); }); observer.observe(document.body, { childList: true, subtree: true }); } })();