영상을 다운로드하는 yt-dlp 명령어를 생성
// ==UserScript==
// @name ohli24 downloader
// @namespace https://ohli24.net/
// @version 2026-02-27
// @description 영상을 다운로드하는 yt-dlp 명령어를 생성
// @author OMIN7
// @match *://*.ohli24.net/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAMAAADwSaEZAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB7FBMVEUAAABEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotJEotL///+I0xiUAAAAonRSTlMAAAMRBSZ34eN2JQhq0PzOaHrv7nliYMTBI/rz29b0+XTxGQkYh/bXGh3YzwYHHCIBb9zwsk9A6uYVsTWD/mYPhJogxUznexKj++RfPsqTJPU4oPJDEGTV3xcCPLqkK4BWVMflWLCXrURdbN7ibQsxOgRTy4iPVTu1pS7D2f1uFL/ti1sMKhZwOeyUIWXTMEbNLKngqy0TeKc9YXx+adFFJ3HSnOCkAAAAAWJLR0Sjx9rvGgAAAAd0SU1FB+QJBw8cNWped78AAAKISURBVFjD7dj5WxJBGAfwfZerWsEUKiWBAMsLiCKzMrrMY+0i0TIzOq0sKtEuM7P7sMvu+9q/tNmdATskdnnnt/j+BM8+z+fZ2Xnf2ZkVhFJKyRsA0YSMCMAss8Vqm4eKbf4Cs6aBVGZ3KMg4yhdKoFoVlVhKjdNFNFjExSLaYhAsS/hYilJVLVjd6o+lNR5Earyq4VsmaJbfFZCCRUeqXb5CVdyCdod19YAq1IZGjaFYUwiHhZt+w3A9FCphuUAostIULTw5ejAIr1odW9NceKp1YWtbyMV1rvUAHLAyn9YnG1oLDFUXtpEudHHPps2AxrZkV03/1m3/GqoxTHG0bQ8CL0xR2js6gRumeLtaRcBicls34zw78gxVP+bd2bwrTrXK3XvmnAf9WGIvVPckqSZX9c7VD4Yw6Nu3nw21v+MA4DABBg4OsslIHEr91Q/GMLIfCRxup1q88UgYUBj5Ix495qbc8RN/3JthjAzV0sOGOmRCYxA4KVPsVB8Sg4HTg2yYsWFAYaQ6zpxl1ZE+dx43AXChK0Yp39BIBlUaEB4dYx2VvNiALNrIJSdb1uyXg6h28l4ZucpKwj9+Ddno8sT1SWrZbkxhl6Bs5Ju9GfTiyHLrdj2vZXty7M5dXi+UxPi9KK9XXbf1Pv4l/EDbHsgTeZ+8Eexhmlx8NP2Yy8Yl8yTtezoscdlSkc3es+eRDJ/Nnu7wxWY4YuK0ukDZ+WDw4uWr/tdvuBx3BIi+TaXIjkvDPAEclg09Ilpriz4iSubZrn2nacmiD6915e9Hc5XI4Vjd8iGLwUcnFnN/ymFSBVaTP+ceGkhfvuI+kji/zdYChL7jPt/8MP9SWNgPSzqWlVL+8/wEmVmqzHr2JDEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDktMDdUMTU6Mjg6NTMrMDI6MDA3vHR4AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA5LTA3VDE1OjI4OjUzKzAyOjAwRuHMxAAAAFd6VFh0UmF3IHByb2ZpbGUgdHlwZSBpcHRjAAB4nOPyDAhxVigoyk/LzEnlUgADIwsuYwsTIxNLkxQDEyBEgDTDZAMjs1Qgy9jUyMTMxBzEB8uASKBKLgDqFxF08kI1lQAAAABJRU5ErkJggg==
// @grant GM_setClipboard
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// 1. 단일 및 전체 화 제목 추출 / 버튼 생성
// ==========================================
if (window.location.hostname.includes('ohli24.net')) {
let videoFileName = 'output.mp4'; // 제목 추출 실패 시 대비용 최소한의 기본값
// 전체 화 일괄 추출을 위한 백그라운드 상태 관리 변수들 (필수)
let isBatchExtracting = false;
let batchIndex = 0;
let batchCommands = [];
let batchLinks = [];
let hiddenIframe = null;
let batchTimeout = null;
window.addEventListener('DOMContentLoaded', () => {
// [단일 화] 페이지 제목을 가져와 파일명으로 덮어쓰기
try {
const ogTitleMeta = document.querySelector('meta[property="og:title"]');
if (ogTitleMeta && ogTitleMeta.content) {
videoFileName = ogTitleMeta.content.replace(/[\\/:*?"<>|]/g, '_').trim() + '.mp4';
}
} catch (e) {}
// [전체 화] 에피소드 목록 확인 후 버튼 생성
if (window === window.top) {
const epSpans = document.querySelectorAll('span.eps-date');
if (epSpans.length > 0) {
const links = Array.from(epSpans)
.map(s => s.parentElement)
.filter(a => a.tagName === 'A' && a.href);
const uniqueLinks = [];
const seen = new Set();
for (const link of links) {
if (!seen.has(link.href)) {
seen.add(link.href);
uniqueLinks.push(link);
}
}
if (uniqueLinks.length > 0) {
createBatchButtonOutside(uniqueLinks);
}
}
}
});
// iframe에서 전달한 securedLink 수신
window.addEventListener('message', (event) => {
if (event.data && event.data.action === 'm3u8_url_extracted') {
if (isBatchExtracting && hiddenIframe && event.source === hiddenIframe.contentWindow) {
// 일괄 추출 진행 중일 때
clearTimeout(batchTimeout);
const url = event.data.url;
const fileName = hiddenIframe.dataset.expectedFileName || `output_${batchIndex}.mp4`;
const ytdlpCommand = `yt-dlp -N 32 -o "${fileName}" "${url}"`;
batchCommands.push(ytdlpCommand);
batchIndex++;
loadNextEpisode();
}
else if (!isBatchExtracting) {
// 단일 추출일 때
createButtonOutside(event.data.url, videoFileName);
}
}
});
function createButtonOutside(url, fileName) {
if (document.getElementById('m3u8-copy-btn-outside')) return;
const btn = document.createElement('button');
btn.id = 'm3u8-copy-btn-outside';
btn.innerText = '현재 화 yt-dlp 복사';
Object.assign(btn.style, {
position: 'fixed',
bottom: '30px',
right: '30px',
zIndex: '2147483647',
padding: '15px 20px',
background: '#0984e3',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '14px',
boxShadow: '0 4px 10px rgba(0,0,0,0.3)'
});
btn.onclick = function() {
const ytdlpCommand = `yt-dlp -N 32 -o "${fileName}" "${url}"`;
try {
GM_setClipboard(ytdlpCommand);
alert('✅ 명령어 추출이 완료되었습니다! 클립보드에 복사되었으니 터미널(CMD)에 그대로 붙여넣기 하세요.\n\nPC 사양에 따라 숫자(32)를 조절하세요. [숫자가 높을수록 빠릅니다.]');
} catch (err) {
alert('복사 실패 (권한 오류): ' + err);
}
};
document.body.appendChild(btn);
}
function createBatchButtonOutside(links) {
if (document.getElementById('m3u8-batch-btn')) return;
const btn = document.createElement('button');
btn.id = 'm3u8-batch-btn';
btn.innerText = '모든 화 yt-dlp 일괄 복사';
Object.assign(btn.style, {
position: 'fixed',
bottom: '85px',
right: '30px',
zIndex: '2147483647',
padding: '15px 20px',
background: '#e84393',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '14px',
boxShadow: '0 4px 10px rgba(0,0,0,0.3)'
});
btn.onclick = function() {
if (isBatchExtracting) return;
if (!confirm(`총 ${links.length}화의 영상 주소를 추출합니다.\n진행하시겠습니까?`)) return;
btn.disabled = true;
btn.style.opacity = '0.7';
startBatchExtraction(links);
};
document.body.appendChild(btn);
}
function updateBatchButtonText(text) {
const btn = document.getElementById('m3u8-batch-btn');
if (btn) btn.innerText = text;
}
function startBatchExtraction(links) {
isBatchExtracting = true;
batchLinks = links;
batchLinks.reverse(); // 1화부터 순서대로 다운로드되도록 배열 뒤집기
batchIndex = 0;
batchCommands = [];
hiddenIframe = document.createElement('iframe');
hiddenIframe.style.display = 'none';
document.body.appendChild(hiddenIframe);
loadNextEpisode();
}
async function loadNextEpisode() {
if (batchIndex >= batchLinks.length) {
finishBatchExtraction();
return;
}
const epLink = batchLinks[batchIndex];
updateBatchButtonText(`추출 중... (${batchIndex + 1}/${batchLinks.length})`);
try {
const res = await fetch(epLink.href);
const html = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const ogTitleMeta = doc.querySelector('meta[property="og:title"]');
let fileName = `output_${batchIndex + 1}.mp4`;
if (ogTitleMeta && ogTitleMeta.content) {
fileName = ogTitleMeta.content.replace(/[\\/:*?"<>|]/g, '_').trim() + '.mp4';
} else {
let epTitleText = epLink.textContent.trim().replace(/[\\/:*?"<>|]/g, '_');
fileName = `${epTitleText}.mp4`;
}
const match = html.match(/<iframe[^>]+src=["'](https?:\/\/[^"']*cdndania[^"']+)["']/i);
if (match && match[1]) {
const iframeUrl = match[1];
hiddenIframe.dataset.expectedFileName = fileName;
hiddenIframe.src = iframeUrl;
clearTimeout(batchTimeout);
batchTimeout = setTimeout(() => {
batchIndex++;
loadNextEpisode();
}, 10000);
} else {
batchIndex++;
loadNextEpisode();
}
} catch (e) {
batchIndex++;
loadNextEpisode();
}
}
function finishBatchExtraction() {
isBatchExtracting = false;
if (hiddenIframe) {
hiddenIframe.remove();
hiddenIframe = null;
}
const finalScript = batchCommands.join(' && ');
try {
GM_setClipboard(finalScript);
alert(`✅ 총 ${batchCommands.length}화의 명령어 추출이 완료되었습니다!\n클립보드에 복사되었으니 터미널(CMD)에 그대로 붙여넣기 하세요.\n\nPC 사양에 따라 숫자(32)를 조절하세요. [숫자가 높을수록 빠릅니다.]`);
} catch (err) {
alert('복사 실패: ' + err);
}
updateBatchButtonText('✅ 모든 화 명령어 복사 완료!');
setTimeout(() => {
const btn = document.getElementById('m3u8-batch-btn');
if(btn) {
btn.innerText = '모든 화 yt-dlp 일괄 복사';
btn.style.opacity = '1';
btn.disabled = false;
}
}, 3000);
}
return;
}
// ==========================================
// 2. 통신 가로채기 및 securedLink 추출
// ==========================================
if (window.location.hostname.includes('cdndania.com')) {
const originalXHR = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(method, url) {
if (url && typeof url === 'string' && (url.includes('/player/index.php') || url.includes('getVideo'))) {
this.addEventListener('load', function() {
const rawData = this.response || this.responseText;
extractAndSendMessage(rawData);
});
}
originalXHR.apply(this, arguments);
};
function extractAndSendMessage(data) {
if (!data) return;
let securedLink = null;
if (typeof data === 'object' && data !== null) {
securedLink = data.securedLink;
} else if (typeof data === 'string') {
try {
const jsonObj = JSON.parse(data);
securedLink = jsonObj.securedLink;
} catch (e) {
const linkMatch = data.match(/"?securedLink"?\s*:\s*["']([^"']+)["']/i);
if (linkMatch && linkMatch[1]) {
securedLink = linkMatch[1].replace(/\\\//g, '/');
}
}
}
if (securedLink) {
window.parent.postMessage({ action: 'm3u8_url_extracted', url: securedLink }, '*');
}
}
}
})();