Update viewer image loading and lazy-loading logic
This commit is contained in:
@@ -180,21 +180,83 @@
|
||||
return comicInfo;
|
||||
}
|
||||
|
||||
const waitForViewerReady = async (timeout = 12000) => {
|
||||
const viewerSelectors = [
|
||||
'.vw-imgs',
|
||||
'.vw-imgs--double',
|
||||
'.vw-imgs--single',
|
||||
'.webtoon-page',
|
||||
'.manhwa-page',
|
||||
'[class*="vw-imgs"]',
|
||||
'.viewer',
|
||||
'[role="main"]'
|
||||
];
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
for (const selector of viewerSelectors) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) return el;
|
||||
}
|
||||
const imgs = Array.from(document.querySelectorAll('img')).filter(img => img.naturalWidth >= 120 && img.naturalHeight >= 120);
|
||||
if (imgs.length > 0) return document;
|
||||
await sleep(300);
|
||||
}
|
||||
return document;
|
||||
};
|
||||
|
||||
function getEpisodeImages(container) {
|
||||
const root = container || document;
|
||||
const imgs = root.querySelectorAll(
|
||||
'.vw-imgs img, .vw-imgs--double img, .vw-imgs--single img, ' +
|
||||
'.webtoon-page img, .manhwa-page img, ' +
|
||||
'[class*="vw-imgs"] img'
|
||||
);
|
||||
// 만화/웹툰 뷰어 영역에서만 이미지 수집
|
||||
const viewerSelectors = [
|
||||
'.vw-imgs',
|
||||
'.vw-imgs--double',
|
||||
'.vw-imgs--single',
|
||||
'.webtoon-page',
|
||||
'.manhwa-page',
|
||||
'[class*="vw-imgs"]',
|
||||
'.viewer'
|
||||
];
|
||||
|
||||
let imgs = [];
|
||||
for (const selector of viewerSelectors) {
|
||||
try {
|
||||
const containers = root.querySelectorAll(selector);
|
||||
if (containers.length > 0) {
|
||||
containers.forEach(containerEl => {
|
||||
imgs.push(...Array.from(containerEl.querySelectorAll('img')));
|
||||
});
|
||||
if (imgs.length > 0) break;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// 찾지 못했으면 전체 img에서 필터링
|
||||
if (imgs.length === 0) {
|
||||
imgs = Array.from(root.querySelectorAll('img')).filter(img => {
|
||||
const rect = img.getBoundingClientRect();
|
||||
return rect.height > 50 && rect.width > 50;
|
||||
});
|
||||
}
|
||||
|
||||
const urls = [];
|
||||
const seen = new Set();
|
||||
imgs.forEach(img => {
|
||||
const src = img.src || img.getAttribute('src') || '';
|
||||
const src = img.src || img.getAttribute('src') || img.getAttribute('data-src') || '';
|
||||
if (src && src.startsWith('http') && !seen.has(src)) {
|
||||
const lowerSrc = src.toLowerCase();
|
||||
if (!lowerSrc.includes('avatar') &&
|
||||
!lowerSrc.includes('logo') &&
|
||||
!lowerSrc.includes('icon') &&
|
||||
!lowerSrc.includes('banner') &&
|
||||
!lowerSrc.includes('nav') &&
|
||||
!lowerSrc.includes('header') &&
|
||||
!lowerSrc.includes('footer') &&
|
||||
!lowerSrc.includes('.svg') &&
|
||||
!lowerSrc.includes('placeholder')) {
|
||||
seen.add(src);
|
||||
urls.push(src);
|
||||
}
|
||||
}
|
||||
});
|
||||
return urls;
|
||||
}
|
||||
@@ -252,41 +314,82 @@
|
||||
|
||||
// lazy loading 트리거: data-src 복사 + loading=eager + 스크롤
|
||||
const triggerLazyLoading = async () => {
|
||||
const viewer = document.querySelector('.vw-imgs, [class*="vw-imgs"]') || document;
|
||||
// 우선 현재 img들 강제 로딩
|
||||
const viewer = document.querySelector('.vw-imgs, [class*="vw-imgs"], .viewer') || document.body;
|
||||
forceLoadImages(viewer);
|
||||
// 동적 스크롤: 새 콘텐츠가 계속 로드되면 scrollHeight도 계속 증가
|
||||
|
||||
let prevHeight = 0;
|
||||
let stuckCount = 0;
|
||||
while (stuckCount < 3) {
|
||||
let maxAttempts = 100; // 더 많은 시도
|
||||
let attemptCount = 0;
|
||||
|
||||
while (stuckCount < 10 && attemptCount < maxAttempts) {
|
||||
attemptCount++;
|
||||
const maxScroll = Math.max(
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.scrollHeight, 0
|
||||
document.body.scrollHeight,
|
||||
viewer.scrollHeight,
|
||||
0
|
||||
);
|
||||
|
||||
log(`[${attemptCount}] 높이: ${maxScroll}px (이전: ${prevHeight}px)`);
|
||||
|
||||
if (maxScroll === prevHeight) {
|
||||
stuckCount++;
|
||||
log(`정체 카운트: ${stuckCount}/10`);
|
||||
} else {
|
||||
stuckCount = 0;
|
||||
log(`새 콘텐츠 감지!`);
|
||||
}
|
||||
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));
|
||||
await sleep(300);
|
||||
|
||||
// 작은 step으로 천천히 스크롤 (모든 이미지 트리거)
|
||||
const step = Math.max(50, Math.min(100, maxScroll / 20)); // 50-100px 범위로 조정
|
||||
let currentPos = window.scrollY;
|
||||
|
||||
while (currentPos < maxScroll) {
|
||||
window.scrollTo(0, currentPos);
|
||||
await sleep(50); // 더 빠른 반응
|
||||
currentPos += step;
|
||||
}
|
||||
|
||||
// 정확히 끝까지
|
||||
window.scrollTo(0, maxScroll);
|
||||
// 스크롤 후 추가된 img들도 강제 로딩 (백그라운드 탭 IO 대체)
|
||||
await sleep(150);
|
||||
|
||||
// 중간중간 강제 로딩
|
||||
forceLoadImages(viewer);
|
||||
await sleep(1500);
|
||||
await sleep(400);
|
||||
}
|
||||
// 끝까지 다 내려간 상태에서 여유 대기 + 최종 강제 로딩
|
||||
|
||||
// 최종 확인: 절대 끝까지
|
||||
const finalHeight = Math.max(
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.scrollHeight,
|
||||
viewer.scrollHeight
|
||||
);
|
||||
log(`최종 높이: ${finalHeight}px - 끝까지 스크롤`);
|
||||
window.scrollTo(0, finalHeight);
|
||||
await sleep(250);
|
||||
forceLoadImages(viewer);
|
||||
await sleep(2000);
|
||||
window.scrollTo(0, 0);
|
||||
await sleep(1000);
|
||||
await sleep(250);
|
||||
window.scrollTo(0, finalHeight);
|
||||
await sleep(375);
|
||||
|
||||
log('스크롤 완료');
|
||||
};
|
||||
|
||||
// 헬퍼: 시리즈명에서 작가 분리 ("시리즈 - 작가" 형식 → {series, writer})
|
||||
function extractSeriesAndWriter(fullSeriesName) {
|
||||
const parts = fullSeriesName.split(' - ');
|
||||
if (parts.length >= 2) {
|
||||
return {
|
||||
series: parts[0].trim(),
|
||||
writer: parts[1].trim()
|
||||
};
|
||||
}
|
||||
return { series: fullSeriesName, writer: '' };
|
||||
}
|
||||
|
||||
// 공통: 이미지 URL 목록을 받아 ZIP 생성 + 다운로드
|
||||
const downloadImagesAsZip = async (imageUrls, seriesName, episodeTitle, episodeNumber) => {
|
||||
const zip = new JSZip();
|
||||
@@ -315,8 +418,13 @@
|
||||
if ((i + 1) % 5 === 0) await sleep(1000);
|
||||
}
|
||||
|
||||
const comicInfo = generateComicInfo(episodeTitle, seriesName, '', episodeNumber, imageUrls.length, imageUrls.length);
|
||||
// ComicInfo: 시리즈와 작가 분리, Title은 "시리즈명 + 에피소드제목"으로 조합
|
||||
const { series: cleanSeries, writer } = extractSeriesAndWriter(seriesName);
|
||||
const fullTitle = `${cleanSeries} ${episodeTitle}`;
|
||||
const comicInfo = generateComicInfo(fullTitle, cleanSeries, writer, episodeNumber, imageUrls.length, imageUrls.length);
|
||||
zip.file('ComicInfo.xml', comicInfo);
|
||||
|
||||
// ZIP 파일명은 원본 그대로 유지
|
||||
const blob = await zip.generateAsync({ type: "blob" });
|
||||
const filename = `${seriesName} - ${episodeTitle}.zip`.replace(/[/\\:*?"<>|]/g, '_');
|
||||
downloadZip(blob, filename);
|
||||
@@ -325,19 +433,31 @@
|
||||
|
||||
// 공통: 현재 페이지에서 이미지 획득 + lazy 로딩 트리거
|
||||
const getImagesWithLazyLoad = async () => {
|
||||
let urls = getEpisodeImages();
|
||||
const viewer = await waitForViewerReady();
|
||||
let urls = getEpisodeImages(viewer);
|
||||
log(`초기 이미지 감지: ${urls.length}개`);
|
||||
|
||||
if (urls.length === 0) {
|
||||
log('이미지 없음 - 0.3초 대기 후 재시도...');
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await sleep(500);
|
||||
urls = getEpisodeImages();
|
||||
if (urls.length > 0) break;
|
||||
await sleep(300);
|
||||
urls = getEpisodeImages(viewer);
|
||||
if (urls.length > 0) {
|
||||
log(`${i+1}번 시도 후 ${urls.length}개 감지`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (urls.length <= 3) {
|
||||
}
|
||||
|
||||
// 실제 만화 이미지가 충분하지 않으면 lazy loading 트리거
|
||||
const hasLargeImages = urls.some(src => src.match(/\.(jpe?g|png|webp|gif)(\?|$)/i));
|
||||
if (urls.length <= 8 || !hasLargeImages) {
|
||||
log(`lazy loading 트리거 (${urls.length}개 → 전체)...`);
|
||||
await triggerLazyLoading();
|
||||
urls = getEpisodeImages();
|
||||
urls = getEpisodeImages(viewer);
|
||||
log(`스크롤 후 감지: ${urls.length}개`);
|
||||
}
|
||||
|
||||
return urls;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user