您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
우클릭 시 유튜브 썸네일 미니플레이어 생성
// ==UserScript== // @name KometTube // @namespace Violentmonkey Scripts // @version 250704 // @description 우클릭 시 유튜브 썸네일 미니플레이어 생성 // @match *://www.youtube.com/* // @grant none // @icon https://i.imgur.com/3fkv1pI.png // @license MIT // ==/UserScript== (function () { 'use strict'; let commentOpened = false; let originalPlayerWidth = null; function extractVideoId(url) { const match = url.match(/v=([^&]+)/) || url.match(/\/shorts\/([^/?]+)/); return match ? match[1] : null; } function copyToClipboard(text, btn) { navigator.clipboard.writeText(text).then(() => { if (btn) { const original = btn.textContent; btn.textContent = 'Copied!'; btn.style.background = '#4ea1f3'; btn.style.color = '#fff'; btn.disabled = true; setTimeout(() => { btn.textContent = original; btn.style.background = '#222'; btn.style.color = '#fff'; btn.disabled = false; }, 3000); } }); } function resizePlayer() { const wrapper = document.getElementById('vm-wrapper'); const player = document.getElementById('vm-mini-player'); if (!wrapper || !player) return; const vw = window.innerWidth; const vh = window.innerHeight; let width = Math.min(vw * (commentOpened ? 0.7 : 0.85), 1280); let height = width * 9 / 16; if (height > vh * 0.85) { height = vh * 0.85; width = height * 16 / 9; } player.style.width = `${width}px`; player.style.maxHeight = `${vh * 0.85}px`; wrapper.style.transform = 'translate(-50%, -50%)'; } function createMiniPlayer(videoId) { if (document.getElementById('vm-wrapper')) return; const overlay = document.createElement('div'); overlay.id = 'vm-overlay'; Object.assign(overlay.style, { position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(5px)', zIndex: 9998 }); const wrapper = document.createElement('div'); wrapper.id = 'vm-wrapper'; Object.assign(wrapper.style, { position: 'fixed', top: '50%', left: '50%', display: 'flex', alignItems: 'flex-start', gap: '12px', zIndex: 9999, transform: 'translate(-50%, -50%)' }); const player = document.createElement('div'); player.id = 'vm-mini-player'; Object.assign(player.style, { background: '#111', padding: '12px', borderRadius: '8px', color: '#fff', display: 'flex', flexDirection: 'column', boxShadow: '0 0 20px rgba(0,0,0,0.5)', overflow: 'hidden' }); const titleBar = document.createElement('div'); titleBar.textContent = '제목 불러오는 중...'; Object.assign(titleBar.style, { fontSize: '18px', fontWeight: 'bold', marginBottom: '8px', wordBreak: 'break-word', lineHeight: '1.4' }); const iframeWrapper = document.createElement('div'); Object.assign(iframeWrapper.style, { position: 'relative', paddingBottom: '56.25%', height: 0, marginBottom: '8px' }); const iframe = document.createElement('iframe'); iframe.src = `https://www.youtube.com/embed/${videoId}?autoplay=1&origin=${location.origin}`; iframe.allow = 'autoplay; encrypted-media'; iframe.allowFullscreen = true; iframe.frameBorder = '0'; // 추가: referrerpolicy iframe.referrerPolicy = 'origin'; Object.assign(iframe.style, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }); iframeWrapper.appendChild(iframe); const btnRow = document.createElement('div'); btnRow.style.marginTop = '4px'; const btnCopy = makeButton('🔗 공유', function() { copyToClipboard(`https://youtu.be/${videoId}`, this); }); const btnComments = makeButton('💬 댓글'); // 좋아요 버튼 관련 코드 전체 삭제 // [btnCopy, btnLike, btnComments].forEach(btn => { // btn.style.marginRight = '6px'; // btnRow.appendChild(btn); // }); [btnCopy, btnComments].forEach(btn => { btn.style.marginRight = '6px'; btnRow.appendChild(btn); }); player.appendChild(titleBar); player.appendChild(iframeWrapper); player.appendChild(btnRow); wrapper.appendChild(player); document.body.appendChild(overlay); document.body.appendChild(wrapper); window.addEventListener('resize', resizePlayer); resizePlayer(); overlay.addEventListener('click', () => { overlay.remove(); wrapper.remove(); commentOpened = false; }); fetch(`https://www.youtube.com/watch?v=${videoId}`) .then(res => res.text()) .then(html => { // 더 유연한 정규식으로 ytInitialPlayerResponse 파싱 const match = html.match(/(?:var |let |const )?ytInitialPlayerResponse\s*=\s*(\{.*?\});/s); if (!match) { console.warn("KometTube: ytInitialPlayerResponse not found"); console.log(html.slice(0, 1000)); // 응답 일부 출력 return; } const data = JSON.parse(match[1]); const title = data.videoDetails?.title || ''; const channel = data.videoDetails?.author || ''; const chId = data.videoDetails?.channelId || ''; const date = data.microformat?.playerMicroformatRenderer?.uploadDate || ''; const isLive = data.videoDetails?.isLiveContent; titleBar.textContent = ''; const link = document.createElement('a'); link.href = `https://www.youtube.com/channel/${chId}`; link.target = '_blank'; link.textContent = channel; link.style.color = '#4ea1f3'; link.style.textDecoration = 'none'; // 제목 표시줄 titleBar.append(link, ` - ${title}`); // 업로드 날짜를 공유 버튼 오른쪽에 표시 (KST 변환 및 조회수 추가) if (date) { // date는 YYYY-MM-DD 또는 YYYY-MM-DDThh:mm:ssZ 등 다양한 형식일 수 있음 let dateObj; if (/^\d{4}-\d{2}-\d{2}$/.test(date)) { // YYYY-MM-DD 형식 dateObj = new Date(date + 'T00:00:00+09:00'); // KST로 직접 지정 } else { // 기타 형식 (이미 시간대가 포함된 경우) dateObj = new Date(date); // UTC 기준이면 KST로 변환 if (!isNaN(dateObj.getTime()) && date.endsWith('Z')) { dateObj = new Date(dateObj.getTime() + 9 * 60 * 60 * 1000); } } // 날짜 파싱이 실패하면 NaN이 됨 let kstStr = ''; if (!isNaN(dateObj.getTime())) { const y = dateObj.getFullYear(); const m = String(dateObj.getMonth()+1).padStart(2,'0'); const d = String(dateObj.getDate()).padStart(2,'0'); const hh = String(dateObj.getHours()).padStart(2,'0'); const mm = String(dateObj.getMinutes()).padStart(2,'0'); const ss = String(dateObj.getSeconds()).padStart(2,'0'); kstStr = `${y}-${m}-${d} ${hh}:${mm}:${ss}`; } else { kstStr = date; } // 조회수 let viewCount = data.videoDetails?.viewCount; if (viewCount) { viewCount = Number(viewCount).toLocaleString('ko-KR') + '회'; } else { viewCount = ''; } // 구분 공간 const spacer = document.createElement('span'); spacer.style.display = 'inline-block'; spacer.style.width = '16px'; btnCopy.insertAdjacentElement('afterend', spacer); // 조회수 const viewSpan = document.createElement('span'); viewSpan.textContent = viewCount; viewSpan.style.marginRight = '8px'; viewSpan.style.fontSize = '14px'; viewSpan.style.color = '#aaa'; spacer.insertAdjacentElement('afterend', viewSpan); // 날짜 const dateSpan = document.createElement('span'); dateSpan.textContent = kstStr; dateSpan.style.fontSize = '14px'; dateSpan.style.color = '#aaa'; viewSpan.insertAdjacentElement('afterend', dateSpan); } if (!isLive) { btnComments.remove(); } else { btnComments.onclick = () => toggleComments(videoId); } }); } function toggleComments(videoId) { const wrapper = document.getElementById('vm-wrapper'); const player = document.getElementById('vm-mini-player'); const existing = document.getElementById('vm-comment-panel'); if (!commentOpened && !existing) { commentOpened = true; originalPlayerWidth = player.offsetWidth; const panel = document.createElement('iframe'); panel.id = 'vm-comment-panel'; panel.src = `https://www.youtube.com/live_chat?v=${videoId}&embed_domain=${location.hostname}`; Object.assign(panel.style, { width: '320px', height: `${player.offsetHeight}px`, border: 'none', borderRadius: '8px', background: '#fff', boxShadow: '0 0 8px rgba(0,0,0,0.3)' }); wrapper.appendChild(panel); player.style.width = `${originalPlayerWidth - 332}px`; } else { commentOpened = false; existing?.remove(); player.style.width = `${originalPlayerWidth}px`; } resizePlayer(); } function makeButton(text, onClick) { const btn = document.createElement('button'); btn.textContent = text; Object.assign(btn.style, { cursor: 'pointer', padding: '4px 8px', borderRadius: '4px', border: '1px solid #444', background: '#222', color: '#fff' }); if (onClick) btn.onclick = onClick; return btn; } document.addEventListener('contextmenu', e => { const link = e.target.closest('a'); if (!link || !link.href) return; const videoId = extractVideoId(link.href); if (videoId) { e.preventDefault(); createMiniPlayer(videoId); } }, true); })();