// ==UserScript==
// @name         tokiDownloader
// @namespace    https://github.com/crossSiteKikyo/tokiDownloader
// @version      0.0.5
// @description  일일툰 다운로더
// @author       hehaho
// @match        https://*.com/webtoon/*
// @match        https://*.com/novel/*
// @match        https://*.net/comic/*
// @match        http://*.cprapid.com:*/bbs/board.php*
// @match        https://*.spotv24.com/bbs/board.php*
// @icon         https://github.com/user-attachments/assets/99f5bb36-4ef8-40cc-8ae5-e3bf1c7952ad
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.js
// @run-at       document-end
// @license      MIT
// @downloadURL https://update.sleazyfork.org/scripts/531932/tokiDownloader.user.js
// @updateURL https://update.sleazyfork.org/scripts/531932/tokiDownloader.meta.js
// ==/UserScript==

(function () {
    'use strict';

    // 파일 다운로드 헬퍼
    const downloadZip = (blob, filename) => {
        const url = URL.createObjectURL(blob);
        if (typeof GM_download !== "undefined") {
            GM_download({
                url: url,
                name: filename,
                onload: () => URL.revokeObjectURL(url),
                onerror: () => {
                    const link = document.createElement("a");
                    link.href = url;
                    link.download = filename;
                    link.click();
                    URL.revokeObjectURL(url);
                }
            });
        } else {
            const link = document.createElement("a");
            link.href = url;
            link.download = filename;
            link.click();
            URL.revokeObjectURL(url);
        }
    };

    // 글로벌 이미지 URL 저장소 (네트워크 모니터링용)
    window.capturedImageUrls = [];
    
    // fetch 인터셉트 (모든 페이지에서 실행)
    const originalFetch = window.fetch;
    window.fetch = function(...args) {
        const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
        if (url && (url.includes('pl3040.com') || url.includes('www.pl3040.com')) && 
            (url.includes('.jpg') || url.includes('.png') || url.includes('.webp'))) {
            if (!window.capturedImageUrls.includes(url)) {
                window.capturedImageUrls.push(url);
            }
        }
        return originalFetch.apply(this, args);
    };

    // XMLHttpRequest 인터셉트
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, ...rest) {
        if (url && (url.includes('pl3040.com') || url.includes('www.pl3040.com')) && 
            (url.includes('.jpg') || url.includes('.png') || url.includes('.webp'))) {
            if (!window.capturedImageUrls.includes(url)) {
                window.capturedImageUrls.push(url);
            }
        }
        return originalOpen.apply(this, [method, url, ...rest]);
    };

    let site = '뉴토끼'; // 예시
    let protocolDomain = 'https://newtoki350.com'; // 예시
    let isListPage = false; // 목록 페이지 여부
    
    // 현재 url 체크
    const currentURL = document.URL;
    if (currentURL.includes('bo_table=toons')) {
        // cprapid.com 또는 spotv24.com
        if (currentURL.includes('wr_id=')) {
            site = "일일툰"; // 개별 에피소드
        } else {
            site = "일일툰_목록"; // 목록 페이지
            isListPage = true;
        }
        protocolDomain = currentURL.match(/^https?:\/\/[^:/?]+/)[0];
    }
    else {
        // 다운 페이지가 아니라면 리턴. @match가 정규표현식이 아니기 때문에 정확한 host를 매치 할 수 없기 때문.
        return;
    }

    function sleep(ms) {
        return new Promise(resolve => {
            setTimeout(() => resolve(), ms);
        })
    }

    function extractComicMetadata() {
        let seriesName = '';
        let writerName = '';

        const pageTitle = document.querySelector('h1');
        if (pageTitle) {
            const titleText = pageTitle.innerText.trim();
            seriesName = titleText.split('>')[0].trim().replace(/\s*\([^)]*\)$/, '');
        }

        // 작가 추출 (작가 라벨 다음의 링크)
        const viewContents = document.querySelectorAll('.view-content');
        for (let elem of viewContents) {
            if (elem.innerText.includes('작가')) {
                const writerLink = elem.querySelector('a');
                if (writerLink) {
                    writerName = writerLink.innerText.trim();
                }
                break;
            }
        }

        return { seriesName, writerName };
    }

    function generateComicInfo(title, series, writer, number, pageCount, imageCount = 0) {
        let pagesXml = '';
        if (imageCount > 0) {
            for (let i = 0; i < imageCount; i++) {
                pagesXml += `        <Page Image="${i}" Type="Story"/>\n`;
            }
        }
        
        const comicInfo = `<?xml version="1.0" encoding="utf-8"?>
<ComicInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Title>${title}</Title>
    <Series>${series}</Series>
    <Number>${number}</Number>
    <Count>-1</Count>
    <Volume>-1</Volume>
    <Summary></Summary>
    <Notes></Notes>
    <Year>-1</Year>
    <Month>-1</Month>
    <Writer>${writer}</Writer>
    <Penciller></Penciller>
    <Inker></Inker>
    <Colorist></Colorist>
    <Letterer></Letterer>
    <CoverArtist></CoverArtist>
    <Editor></Editor>
    <Publisher></Publisher>
    <Imprint></Imprint>
    <Genre></Genre>
    <Web></Web>
    <PageCount>${pageCount}</PageCount>
    <LanguageISO>ko</LanguageISO>
    <Format></Format>
    <BlackAndWhite>Unknown</BlackAndWhite>
    <Manga>Yes</Manga>
${pagesXml ? `    <Pages>\n${pagesXml}    </Pages>\n` : ''}
</ComicInfo>`;
        return comicInfo;
    }

    async function tokiDownload(startIndex, lastIndex) {
        try {
            // 목록 페이지 처리
            if (isListPage) {
                console.clear();
                console.log('목록 페이지 처리 시작');
                
                // 에피소드 목록 추출
                const episodeList = document.querySelector('ul.episode-list');
                if (!episodeList) {
                    alert('에피소드 목록을 찾을 수 없습니다.');
                    return;
                }
                
                const items = Array.from(episodeList.querySelectorAll('li'));
                console.log(`총 ${items.length}개 에피소드 발견`);
                
                // 각 에피소드의 링크 추출
                const episodes = [];
                items.forEach((li, index) => {
                    const button = li.querySelector('button.episode');
                    if (button) {
                        const onclick = button.getAttribute('onclick') || '';
                        const match = onclick.match(/location\.href='([^']+)'/);
                        if (match && match[1]) {
                            const relativeUrl = match[1];
                            const absoluteUrl = new URL(relativeUrl, window.location.href).href;
                            const episodeTitle = li.innerText.split('\n')[0].trim();
                            
                            episodes.push({
                                index,
                                title: episodeTitle,
                                url: absoluteUrl
                            });
                        }
                    }
                });
                
                console.log(`추출된 링크: ${episodes.length}개`);
                
                if (episodes.length === 0) {
                    alert('에피소드 링크를 추출할 수 없습니다.');
                    return;
                }
                
                // iframe 생성
                const iframe = document.createElement('iframe');
                iframe.width = 600;
                iframe.height = 600;
                document.body.prepend(iframe);
                
                const waitIframeLoad = (url) => {
                    return new Promise((resolve) => {
                        iframe.addEventListener('load', () => resolve(), { once: true });
                        iframe.src = url;
                    });
                };
                
                // 각 에피소드 다운로드
                for (let i = 0; i < episodes.length; i++) {
                    const episode = episodes[i];
                    console.clear();
                    console.log(`${i + 1}/${episodes.length} ${episode.title} 처리 중...`);
                    
                    // iframe에서 페이지 로드
                    await waitIframeLoad(episode.url);
                    
                    // 이미지 로드 대기 (canvas 렌더링 시간)
                    console.log('이미지 로드 대기 중...');
                    await sleep(3000);
                    
                    // 스크린샷을 통해 이미지 로드 강제 (canvas 렌더링)
                    try {
                        const iframeDoc = iframe.contentWindow.document;
                        const viewContent = iframeDoc.querySelector('.view-content, [class*="viewer"]');
                        if (viewContent) {
                            // 스크롤 트리거 (lazy loading 이미지 로드)
                            viewContent.scrollTop = 100;
                        }
                    } catch (e) {
                        // iframe CORS 문제 무시
                    }
                    
                    // 추가 대기 (네트워크 요청 완료)
                    await sleep(2000);
                    
                    // 이미지 URL 수집 (전역 변수에서)
                    let imageUrls = [];
                    
                    if (window.capturedImageUrls && window.capturedImageUrls.length > 0) {
                        imageUrls = window.capturedImageUrls.slice(); // 복사
                        window.capturedImageUrls = []; // 초기화
                    }
                    
                    if (imageUrls.length === 0) {
                        console.log(`이미지를 찾을 수 없음 - 스킵`);
                        continue;
                    }
                    
                    console.log(`이미지 ${imageUrls.length}개 발견`);
                    
                    // ZIP 생성
                    const zip = new JSZip();
                    
                    // 이미지 다운로드
                    const downloadImage = (url, index, total) => {
                        return new Promise((resolve) => {
                            GM_xmlhttpRequest({
                                method: 'GET',
                                url: url,
                                responseType: 'arraybuffer',
                                headers: {
                                    'Referer': protocolDomain,
                                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                                },
                                onload: (response) => {
                                    if (response.status === 200) {
                                        const blob = new Blob([response.response], { type: 'image/jpeg' });
                                        const filename = `image${String(index).padStart(4, '0')}.jpg`;
                                        zip.file(filename, blob);
                                        console.log(`  ${index + 1}/${total} 이미지 다운로드`);
                                    } else {
                                        console.log(`  이미지 ${index} 실패 (${response.status})`);
                                    }
                                    resolve();
                                },
                                onerror: () => {
                                    console.log(`  이미지 ${index} 오류`);
                                    resolve();
                                },
                                ontimeout: () => {
                                    console.log(`  이미지 ${index} 타임아웃`);
                                    resolve();
                                }
                            });
                        });
                    };
                    
                    let promiseList = [];
                    for (let j = 0; j < imageUrls.length; j++) {
                        promiseList.push(downloadImage(imageUrls[j], j, imageUrls.length));
                        if (promiseList.length >= 5) {
                            await Promise.all(promiseList);
                            promiseList = [];
                            await sleep(1000);
                        }
                    }
                    
                    if (promiseList.length > 0) {
                        await Promise.all(promiseList);
                    }
                    
                    // comicinfo.xml 생성
                    const comicInfoContent = generateComicInfo(episode.title, episode.title, '', String(i + 1), imageUrls.length, imageUrls.length);
                    zip.file('ComicInfo.xml', comicInfoContent);
                    
                    // ZIP 다운로드
                    const content = await zip.generateAsync({ type: "blob" });
                    downloadZip(content, episode.title + '.zip');
                    
                    console.log(`${i + 1}/${episodes.length} ${episode.title} 완료`);
                    
                    // 다운로드 간격
                    await sleep(3000);
                }
                
                iframe.remove();
                console.log(`모든 에피소드 다운로드 완료`);
                return;
            }
            
            // 리스트들 가져오기
            let list = Array.from(document.querySelector('.list-body').querySelectorAll('li')).reverse();

            // 리스트 필터
            // startIndex가 있다면
            if (startIndex) {
                while (true) {
                    let num = parseInt(list[0].querySelector('.wr-num').innerText);
                    if (num < startIndex)
                        list.shift();
                    else
                        break;
                }
            }
            // lastIndex가 있다면
            if (lastIndex) {
                while (true) {
                    let num = parseInt(list.at(-1).querySelector('.wr-num').innerText);
                    if (lastIndex < num)
                        list.pop();
                    else
                        break;
                }
            }

            // iframe생성
            const iframe = document.createElement('iframe');
            iframe.width = 600;
            iframe.height = 600;
            document.querySelector('.content').prepend(iframe);
            const waitIframeLoad = (url) => {
                return new Promise((resolve) => {
                    iframe.addEventListener('load', () => resolve(), { once: true });
                    // iframe.addEventListener('DOMContentLoaded', () => resolve());
                    iframe.src = url;
                });
            };

            if (site === "일일툰") {
                // URL에서 시리즈 ID와 에피소드 ID 추출
                const urlParams = new URLSearchParams(window.location.search);
                const seriesId = urlParams.get('is') || '';
                const episodeId = urlParams.get('wr_id') || '';
                
                if (!seriesId || !episodeId) {
                    alert('시리즈 ID 또는 에피소드 ID를 찾을 수 없습니다.');
                    return;
                }

                // 제목 추출
                const titleElement = document.querySelector('h1');
                const title = titleElement ? titleElement.innerText.trim() : '제목없음';

                // 전역 변수에서 이미 캡처된 이미지 URL 가져오기
                let imageUrls = window.capturedImageUrls || [];

                console.clear();
                console.log(`일일툰 다운로드 시작`);
                console.log(`현재까지 감지된 이미지: ${imageUrls.length}개`);

                // 이미지가 없으면 추가 대기
                if (imageUrls.length === 0) {
                    console.log('이미지 로드 대기 중...');
                    for (let i = 0; i < 10; i++) {
                        await sleep(500);
                        imageUrls = window.capturedImageUrls || [];
                        console.log(`대기 ${i+1}: ${imageUrls.length}개`);
                        if (imageUrls.length > 0) break;
                    }
                }

                if (imageUrls.length === 0) {
                    alert('이미지 URL을 감지하지 못했습니다. 페이지가 완전히 로드되지 않았을 수 있습니다.');
                    return;
                }

                // ZIP 생성
                const zip = new JSZip();

                // 이미지 다운로드 함수
                const downloadImage = (url, index, total) => {
                    return new Promise((resolve) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: url,
                            responseType: 'arraybuffer',
                            headers: {
                                'Referer': protocolDomain,
                                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                            },
                            onload: (response) => {
                                if (response.status === 200) {
                                    const blob = new Blob([response.response], { type: 'image/jpeg' });
                                    const filename = `image${String(index).padStart(4, '0')}.jpg`;
                                    zip.file(filename, blob);
                                    console.log(`${index + 1}/${total} 다운로드 완료`);
                                } else {
                                    console.log(`이미지 ${index} 다운로드 실패 (${response.status})`);
                                }
                                resolve();
                            },
                            onerror: (error) => {
                                console.log(`이미지 ${index} 다운로드 오류:`, error);
                                resolve();
                            },
                            ontimeout: () => {
                                console.log(`이미지 ${index} 다운로드 타임아웃`);
                                resolve();
                            }
                        });
                    });
                };

                // 모든 이미지 다운로드
                let promiseList = [];
                for (let i = 0; i < imageUrls.length; i++) {
                    promiseList.push(downloadImage(imageUrls[i], i, imageUrls.length));
                    // 다운로드 간격 (과도한 요청 방지)
                    if (promiseList.length >= 5) {
                        await Promise.all(promiseList);
                        promiseList = [];
                        await sleep(1000);
                    }
                }
                
                // 남은 요청 처리
                if (promiseList.length > 0) {
                    await Promise.all(promiseList);
                }

                // comicinfo.xml 생성 및 추가
                const comicInfoContent = generateComicInfo(title, title, '', '1', imageUrls.length, imageUrls.length);
                zip.file('ComicInfo.xml', comicInfoContent);

                // ZIP 생성 및 다운로드
                const content = await zip.generateAsync({ type: "blob" });
                    downloadZip(content, title + ".zip");

                console.log(`완료: ${imageUrls.length}개 이미지 다운로드`);
            }

            console.log(`모든 회차 다운완료`);
        } catch (error) {
            alert(`tokiDownload 오류발생: ${site}\n${currentURL}\n` + error);
            console.error(error);
        }
    }

    // ui 추가
    if (isListPage) {
        // 목록 페이지용 메뉴
        GM_registerMenuCommand('[목록] 전체 다운로드', () => tokiDownload());
    } else {
        // 개별 페이지용 메뉴 (기존)
        GM_registerMenuCommand('전체 다운로드', () => tokiDownload());
        GM_registerMenuCommand('N번째 회차부터', () => {
            const startPageInput = prompt('몇번째 회차부터 저장할까요?', 1);
            tokiDownload(startPageInput);
        });
        GM_registerMenuCommand('N번째 회차부터 N번째 까지', () => {
            const startPageInput = prompt('몇번째 회차부터 저장할까요?', 1);
            const endPageInput = prompt('몇번째 회차까지 저장할까요?', 2);
            tokiDownload(startPageInput, endPageInput);
        });
    }
    // Your code here...
})();
