시리즈, 작가명, 파일이름 수정
This commit is contained in:
+44
-32
@@ -1,11 +1,11 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name sbxh6Downloader
|
// @name sbxh6Downloader
|
||||||
// @namespace https://github.com/crossSiteKikyo/tokiDownloader
|
// @namespace https://github.com/crossSiteKikyo/tokiDownloader
|
||||||
// @version 0.2.0
|
// @version 0.1.3
|
||||||
// @description sbxh6.com (뉴토끼) 만화 다운로더
|
// @description sbxh6.com (뉴토끼) 만화 다운로더
|
||||||
// @author hehaho
|
// @author hehaho
|
||||||
// @match https://sbxh*.com/manhwa/*
|
// @match https://sbxh9.com/manhwa/*
|
||||||
// @match https://sbxh*.com/manhwa/*/*
|
// @match https://sbxh9.com/manhwa/*/*
|
||||||
// @icon https://github.com/user-attachments/assets/99f5bb36-4ef8-40cc-8ae5-e3bf1c7952ad
|
// @icon https://github.com/user-attachments/assets/99f5bb36-4ef8-40cc-8ae5-e3bf1c7952ad
|
||||||
// @grant GM_registerMenuCommand
|
// @grant GM_registerMenuCommand
|
||||||
// @grant GM_unregisterMenuCommand
|
// @grant GM_unregisterMenuCommand
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
// @run-at document-start
|
// @run-at document-start
|
||||||
// @license MIT
|
// @license MIT
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@@ -243,21 +242,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSeriesName() {
|
function getSeriesName() {
|
||||||
|
let seriesName = '제목없음';
|
||||||
|
let writer = '';
|
||||||
|
|
||||||
const metaEl = document.querySelector('div.vw-bot-meta strong');
|
const metaEl = document.querySelector('div.vw-bot-meta strong');
|
||||||
if (metaEl) return metaEl.textContent.trim();
|
let rawTitle = '';
|
||||||
|
|
||||||
const titleEl = document.querySelector('h1');
|
|
||||||
if (titleEl && !titleEl.closest('.hero-v2')) {
|
|
||||||
return titleEl.textContent.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (metaEl) {
|
||||||
|
rawTitle = metaEl.textContent.trim();
|
||||||
|
} else {
|
||||||
const ogTitle = document.querySelector('meta[property="og:title"]');
|
const ogTitle = document.querySelector('meta[property="og:title"]');
|
||||||
if (ogTitle) {
|
rawTitle = (ogTitle ? ogTitle.getAttribute('content') || '' : document.title).split('|')[0].trim();
|
||||||
const content = ogTitle.getAttribute('content') || '';
|
|
||||||
return content.split('|')[0].trim() || content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return document.title.split('|')[0].trim() || '제목없음';
|
const parts = rawTitle.split(/\s-\s/);
|
||||||
|
seriesName = parts[0] || rawTitle;
|
||||||
|
if (parts.length > 1) writer = parts[1];
|
||||||
|
|
||||||
|
return { seriesName, writer };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEpisodeTitle() {
|
function getEpisodeTitle() {
|
||||||
@@ -487,9 +489,9 @@
|
|||||||
return urls;
|
return urls;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function downloadImagesAsZip(imageUrls, seriesName, episodeTitle, episodeNumber) {
|
async function downloadImagesAsZip(imageUrls, seriesInfo, episodeTitle, episodeNumber) {
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
const folderName = `${seriesName}_${episodeNumber}`.replace(/[<>:"/\\|?*]/g, '_').slice(0, 200);
|
const folderName = `${seriesInfo.seriesName}_${episodeNumber}`.replace(/[<>:"/\\|?*]/g, '_').slice(0, 200);
|
||||||
const folder = zip.folder(folderName);
|
const folder = zip.folder(folderName);
|
||||||
|
|
||||||
const fetchImage = (url) => new Promise((resolve, reject) => {
|
const fetchImage = (url) => new Promise((resolve, reject) => {
|
||||||
@@ -523,12 +525,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const comicInfoXml = generateComicInfo(
|
const comicInfoXml = generateComicInfo(
|
||||||
episodeTitle, seriesName, '', episodeNumber, total, total
|
episodeTitle,
|
||||||
|
seriesInfo.seriesName,
|
||||||
|
seriesInfo.writer,
|
||||||
|
episodeNumber,
|
||||||
|
total,
|
||||||
|
total
|
||||||
);
|
);
|
||||||
folder.file('ComicInfo.xml', comicInfoXml);
|
folder.file('ComicInfo.xml', comicInfoXml);
|
||||||
|
|
||||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||||
const safeName = `${seriesName}_${episodeNumber}.zip`.replace(/[<>:"/\\|?*]/g, '_');
|
const safeName = `${seriesInfo.seriesName}_${episodeNumber}.zip`.replace(/[<>:"/\\|?*]/g, '_');
|
||||||
downloadZip(zipBlob, safeName);
|
downloadZip(zipBlob, safeName);
|
||||||
log(`downloadImagesAsZip: ${safeName} 저장 완료 (${total}개 중 ${folder.files ? Object.keys(folder.files).length - 1 : '?'}개 성공)`);
|
log(`downloadImagesAsZip: ${safeName} 저장 완료 (${total}개 중 ${folder.files ? Object.keys(folder.files).length - 1 : '?'}개 성공)`);
|
||||||
}
|
}
|
||||||
@@ -543,11 +550,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesName = getSeriesName();
|
const seriesInfo = getSeriesName();
|
||||||
const episodeTitle = getEpisodeTitle();
|
const episodeTitleText = getEpisodeTitle();
|
||||||
log(`시리즈: ${seriesName}, 에피소드: ${episodeTitle}, 이미지: ${imageUrls.length}개`);
|
const episodeTitle = `${seriesInfo.seriesName} ${episodeTitleText}`;
|
||||||
|
const episodeNumber = episodeTitleText.match(/\d+/)?.[0] || '1';
|
||||||
|
|
||||||
await downloadImagesAsZip(imageUrls, seriesName, episodeTitle, '1');
|
log(`시리즈: ${seriesInfo.seriesName}, 작가: ${seriesInfo.writer}, 에피소드: ${episodeTitle}, 이미지: ${imageUrls.length}개`);
|
||||||
|
|
||||||
|
await downloadImagesAsZip(imageUrls, seriesInfo, episodeTitle, episodeNumber);
|
||||||
log('완료');
|
log('완료');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('다운로드 중 오류 발생:\n' + error);
|
alert('다운로드 중 오류 발생:\n' + error);
|
||||||
@@ -582,7 +592,7 @@
|
|||||||
const startQueue = (episodes, seriesName) => {
|
const startQueue = (episodes, seriesName) => {
|
||||||
log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`);
|
log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`);
|
||||||
sessionStorage.setItem(QUEUE_KEY, JSON.stringify(episodes));
|
sessionStorage.setItem(QUEUE_KEY, JSON.stringify(episodes));
|
||||||
sessionStorage.setItem(SERIES_KEY, seriesName);
|
sessionStorage.setItem(SERIES_KEY, JSON.stringify(seriesName));
|
||||||
sessionStorage.setItem(IDX_KEY, '0');
|
sessionStorage.setItem(IDX_KEY, '0');
|
||||||
sessionStorage.setItem(LIST_KEY, window.location.href);
|
sessionStorage.setItem(LIST_KEY, window.location.href);
|
||||||
window.location.href = episodes[0].url;
|
window.location.href = episodes[0].url;
|
||||||
@@ -592,8 +602,8 @@
|
|||||||
log('목록 페이지 처리 시작');
|
log('목록 페이지 처리 시작');
|
||||||
const episodes = getEpisodesFromList();
|
const episodes = getEpisodesFromList();
|
||||||
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
|
if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; }
|
||||||
const seriesName = getSeriesName();
|
const seriesInfo = getSeriesName();
|
||||||
startQueue(episodes, seriesName);
|
startQueue(episodes, seriesInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// n화 이후 다운로드
|
// n화 이후 다운로드
|
||||||
@@ -612,8 +622,8 @@
|
|||||||
.sort((a, b) => parseFloat(a.number) - parseFloat(b.number));
|
.sort((a, b) => parseFloat(a.number) - parseFloat(b.number));
|
||||||
|
|
||||||
if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; }
|
if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; }
|
||||||
const seriesName = getSeriesName();
|
const seriesInfo = getSeriesName();
|
||||||
startQueue(filtered, seriesName);
|
startQueue(filtered, seriesInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 범위 다운로드
|
// 범위 다운로드
|
||||||
@@ -640,8 +650,8 @@
|
|||||||
.sort((a, b) => parseFloat(a.number) - parseFloat(b.number));
|
.sort((a, b) => parseFloat(a.number) - parseFloat(b.number));
|
||||||
|
|
||||||
if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; }
|
if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; }
|
||||||
const seriesName = getSeriesName();
|
const seriesInfo = getSeriesName();
|
||||||
startQueue(filtered, seriesName);
|
startQueue(filtered, seriesInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 자동 큐 처리: sessionStorage에 큐가 있으면 현재 화면을 다운로드 후 다음으로 이동
|
// 자동 큐 처리: sessionStorage에 큐가 있으면 현재 화면을 다운로드 후 다음으로 이동
|
||||||
@@ -661,9 +671,11 @@
|
|||||||
const imageUrls = await getImagesWithLazyLoad();
|
const imageUrls = await getImagesWithLazyLoad();
|
||||||
log(`자동 다운로드: ${idx + 1}/${queue.length} - ${queue[idx].title}, 이미지 ${imageUrls.length}개`);
|
log(`자동 다운로드: ${idx + 1}/${queue.length} - ${queue[idx].title}, 이미지 ${imageUrls.length}개`);
|
||||||
if (imageUrls.length > 0) {
|
if (imageUrls.length > 0) {
|
||||||
const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName();
|
const seriesInfoStr = sessionStorage.getItem(SERIES_KEY);
|
||||||
const episodeTitle = getEpisodeTitle() || queue[idx].title;
|
const seriesInfo = seriesInfoStr ? JSON.parse(seriesInfoStr) : getSeriesName();
|
||||||
await downloadImagesAsZip(imageUrls, seriesName, episodeTitle, queue[idx].number);
|
const episodeTitleText = getEpisodeTitle() || queue[idx].title;
|
||||||
|
const fullEpisodeTitle = `${seriesInfo.seriesName} ${episodeTitleText}`;
|
||||||
|
await downloadImagesAsZip(imageUrls, seriesInfo, fullEpisodeTitle, queue[idx].number);
|
||||||
} else {
|
} else {
|
||||||
log(`이미지 없음 - 스킵`);
|
log(`이미지 없음 - 스킵`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user