diff --git a/tokimonkeyDownader.js b/tokimonkeyDownader.js index 0628f80..6777593 100644 --- a/tokimonkeyDownader.js +++ b/tokimonkeyDownader.js @@ -237,17 +237,17 @@ }; // lazy loading 트리거: data-src 복사 + loading=eager + 스크롤 - const triggerLazyLoading = async () => { - const viewer = document.querySelector('.vw-imgs, [class*="vw-imgs"]') || document; - // 우선 현재 img들 강제 로딩 + const triggerLazyLoading = async (doc, win) => { + const d = doc || document; + const w = win || window; + const viewer = d.querySelector('.vw-imgs, [class*="vw-imgs"]') || d; forceLoadImages(viewer); - // 동적 스크롤: 새 콘텐츠가 계속 로드되면 scrollHeight도 계속 증가 let prevHeight = 0; let stuckCount = 0; while (stuckCount < 3) { const maxScroll = Math.max( - document.documentElement.scrollHeight, - document.body.scrollHeight, 0 + d.documentElement.scrollHeight, + d.body.scrollHeight, 0 ); if (maxScroll === prevHeight) { stuckCount++; @@ -255,21 +255,18 @@ stuckCount = 0; } prevHeight = maxScroll; - // 현재 위치에서 끝까지 step 단위로 스크롤 (느리게) const step = Math.min(300, maxScroll); - for (let s = window.scrollY; s <= maxScroll; s += step) { - window.scrollTo(0, Math.min(s, maxScroll)); + for (let s = w.scrollY; s <= maxScroll; s += step) { + w.scrollTo(0, Math.min(s, maxScroll)); await sleep(300); } - window.scrollTo(0, maxScroll); - // 스크롤 후 추가된 img들도 강제 로딩 (백그라운드 탭 IO 대체) + w.scrollTo(0, maxScroll); forceLoadImages(viewer); await sleep(1500); } - // 끝까지 다 내려간 상태에서 여유 대기 + 최종 강제 로딩 forceLoadImages(viewer); await sleep(2000); - window.scrollTo(0, 0); + w.scrollTo(0, 0); await sleep(1000); }; @@ -309,20 +306,22 @@ log(`저장: ${filename} (${imageUrls.length}개)`); }; - // 공통: 현재 페이지에서 이미지 획득 + lazy 로딩 트리거 - const getImagesWithLazyLoad = async () => { - let urls = getEpisodeImages(); + // 공통: 페이지/iframe에서 이미지 획득 + lazy 로딩 트리거 + const getImagesWithLazyLoad = async (doc, win) => { + const d = doc || document; + const w = win || window; + let urls = getEpisodeImages(d); if (urls.length === 0) { for (let i = 0; i < 10; i++) { await sleep(500); - urls = getEpisodeImages(); + urls = getEpisodeImages(d); if (urls.length > 0) break; } } if (urls.length <= 3) { log(`lazy loading 트리거 (${urls.length}개 → 전체)...`); - await triggerLazyLoading(); - urls = getEpisodeImages(); + await triggerLazyLoading(d, w); + urls = getEpisodeImages(d); } return urls; }; @@ -330,7 +329,7 @@ async function downloadCurrentEpisode() { try { log('에피소드 다운로드 시작...'); - const imageUrls = await getImagesWithLazyLoad(); + const imageUrls = await getImagesWithLazyLoad(document, window); if (imageUrls.length === 0) { alert('이미지를 찾을 수 없습니다.'); @@ -372,14 +371,68 @@ return episodes.length > 0 ? episodes : null; }; - // 큐 저장 후 첫 화로 이동 + let downloadFrame = null; + + // iframe에서 에피소드 처리 + const processEpisodeInFrame = async (episodes, index, seriesName) => { + if (index >= episodes.length) { + log('모든 에피소드 다운로드 완료!'); + if (downloadFrame) { + downloadFrame.remove(); + downloadFrame = null; + } + return; + } + + const episode = episodes[index]; + log(`${index + 1}/${episodes.length} ${episode.title} 로딩 중...`); + + // iframe 생성 (상단 1/3) + if (!downloadFrame) { + downloadFrame = document.createElement('iframe'); + downloadFrame.style.position = 'fixed'; + downloadFrame.style.top = '0'; + downloadFrame.style.left = '0'; + downloadFrame.style.width = '100%'; + downloadFrame.style.height = '33.33vh'; + downloadFrame.style.border = 'none'; + downloadFrame.style.zIndex = '999999'; + document.body.appendChild(downloadFrame); + } + + // 에피소드 로드 + downloadFrame.src = episode.url; + await new Promise((resolve) => { + downloadFrame.addEventListener('load', () => resolve(), { once: true }); + }); + log(`${episode.title} 로드 완료, 이미지 대기 중...`); + await sleep(7000); + + // iframe에서 이미지 추출 + let imageUrls = []; + try { + const iframeWin = downloadFrame.contentWindow; + const iframeDoc = iframeWin.document; + imageUrls = await getImagesWithLazyLoad(iframeDoc, iframeWin); + } catch (e) { + log('iframe 접근 오류, GM_xmlhttpRequest로 대체:', e); + // GM_xmlhttpRequest로 페이지 HTML 받아서 이미지 추출 시도 + // (fallback - 추후 구현) + } + + if (imageUrls.length > 0) { + await downloadImagesAsZip(imageUrls, seriesName, episode.title, episode.number); + } else { + log('이미지 없음 - 스킵'); + } + + await sleep(2000); + processEpisodeInFrame(episodes, index + 1, seriesName); + }; + const startQueue = (episodes, seriesName) => { - log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`); - sessionStorage.setItem(QUEUE_KEY, JSON.stringify(episodes)); - sessionStorage.setItem(SERIES_KEY, seriesName); - sessionStorage.setItem(IDX_KEY, '0'); - sessionStorage.setItem(LIST_KEY, window.location.href); - window.location.href = episodes[0].url; + log(`${episodes.length}개 에피소드 다운로드 시작 (iframe)...`); + processEpisodeInFrame(episodes, 0, seriesName); }; async function downloadAllEpisodes() { @@ -390,7 +443,6 @@ startQueue(episodes, seriesName); } - // n화 이후 다운로드 async function downloadAfterEpisode() { const episodes = getEpisodesFromList(); if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; } @@ -410,7 +462,6 @@ startQueue(filtered, seriesName); } - // 범위 다운로드 async function downloadRangeEpisodes() { const episodes = getEpisodesFromList(); if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; } @@ -452,7 +503,7 @@ await sleep(7000); try { - const imageUrls = await getImagesWithLazyLoad(); + const imageUrls = await getImagesWithLazyLoad(document, window); if (imageUrls.length > 0) { const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName(); const episodeTitle = getEpisodeTitle() || queue[idx].title;