From 31f591605d405824157f64f367615c6f090443c3 Mon Sep 17 00:00:00 2001 From: user01 Date: Wed, 1 Jul 2026 16:26:03 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=EB=A6=AC=EC=A6=88,=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=EB=AA=85,=20=ED=8C=8C=EC=9D=BC=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tokimonkeyDownader.js | 76 +++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/tokimonkeyDownader.js b/tokimonkeyDownader.js index 5a3a571..c95fe55 100644 --- a/tokimonkeyDownader.js +++ b/tokimonkeyDownader.js @@ -1,11 +1,11 @@ // ==UserScript== // @name sbxh6Downloader // @namespace https://github.com/crossSiteKikyo/tokiDownloader - // @version 0.2.0 +// @version 0.1.3 // @description sbxh6.com (뉴토끼) 만화 다운로더 // @author hehaho -// @match https://sbxh*.com/manhwa/* -// @match https://sbxh*.com/manhwa/*/* +// @match https://sbxh9.com/manhwa/* +// @match https://sbxh9.com/manhwa/*/* // @icon https://github.com/user-attachments/assets/99f5bb36-4ef8-40cc-8ae5-e3bf1c7952ad // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand @@ -16,7 +16,6 @@ // @run-at document-start // @license MIT // ==/UserScript== - (function () { 'use strict'; @@ -243,21 +242,24 @@ } function getSeriesName() { + let seriesName = '제목없음'; + let writer = ''; + 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"]'); + rawTitle = (ogTitle ? ogTitle.getAttribute('content') || '' : document.title).split('|')[0].trim(); } - const ogTitle = document.querySelector('meta[property="og:title"]'); - if (ogTitle) { - const content = ogTitle.getAttribute('content') || ''; - return content.split('|')[0].trim() || content; - } + const parts = rawTitle.split(/\s-\s/); + seriesName = parts[0] || rawTitle; + if (parts.length > 1) writer = parts[1]; - return document.title.split('|')[0].trim() || '제목없음'; + return { seriesName, writer }; } function getEpisodeTitle() { @@ -487,9 +489,9 @@ return urls; }; - async function downloadImagesAsZip(imageUrls, seriesName, episodeTitle, episodeNumber) { + async function downloadImagesAsZip(imageUrls, seriesInfo, episodeTitle, episodeNumber) { 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 fetchImage = (url) => new Promise((resolve, reject) => { @@ -523,12 +525,17 @@ } const comicInfoXml = generateComicInfo( - episodeTitle, seriesName, '', episodeNumber, total, total + episodeTitle, + seriesInfo.seriesName, + seriesInfo.writer, + episodeNumber, + total, + total ); folder.file('ComicInfo.xml', comicInfoXml); 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); log(`downloadImagesAsZip: ${safeName} 저장 완료 (${total}개 중 ${folder.files ? Object.keys(folder.files).length - 1 : '?'}개 성공)`); } @@ -543,11 +550,14 @@ return; } - const seriesName = getSeriesName(); - const episodeTitle = getEpisodeTitle(); - log(`시리즈: ${seriesName}, 에피소드: ${episodeTitle}, 이미지: ${imageUrls.length}개`); + const seriesInfo = getSeriesName(); + const episodeTitleText = getEpisodeTitle(); + 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('완료'); } catch (error) { alert('다운로드 중 오류 발생:\n' + error); @@ -582,7 +592,7 @@ const startQueue = (episodes, seriesName) => { log(`${episodes.length}개 에피소드를 큐에 저장, 첫 화로 이동합니다...`); 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(LIST_KEY, window.location.href); window.location.href = episodes[0].url; @@ -592,8 +602,8 @@ log('목록 페이지 처리 시작'); const episodes = getEpisodesFromList(); if (!episodes) { alert('에피소드 목록을 찾을 수 없습니다.'); return; } - const seriesName = getSeriesName(); - startQueue(episodes, seriesName); + const seriesInfo = getSeriesName(); + startQueue(episodes, seriesInfo); } // n화 이후 다운로드 @@ -612,8 +622,8 @@ .sort((a, b) => parseFloat(a.number) - parseFloat(b.number)); if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; } - const seriesName = getSeriesName(); - startQueue(filtered, seriesName); + const seriesInfo = getSeriesName(); + startQueue(filtered, seriesInfo); } // 범위 다운로드 @@ -640,8 +650,8 @@ .sort((a, b) => parseFloat(a.number) - parseFloat(b.number)); if (filtered.length === 0) { alert('조건에 맞는 에피소드가 없습니다.'); return; } - const seriesName = getSeriesName(); - startQueue(filtered, seriesName); + const seriesInfo = getSeriesName(); + startQueue(filtered, seriesInfo); } // 자동 큐 처리: sessionStorage에 큐가 있으면 현재 화면을 다운로드 후 다음으로 이동 @@ -661,9 +671,11 @@ const imageUrls = await getImagesWithLazyLoad(); log(`자동 다운로드: ${idx + 1}/${queue.length} - ${queue[idx].title}, 이미지 ${imageUrls.length}개`); if (imageUrls.length > 0) { - const seriesName = sessionStorage.getItem(SERIES_KEY) || getSeriesName(); - const episodeTitle = getEpisodeTitle() || queue[idx].title; - await downloadImagesAsZip(imageUrls, seriesName, episodeTitle, queue[idx].number); + const seriesInfoStr = sessionStorage.getItem(SERIES_KEY); + const seriesInfo = seriesInfoStr ? JSON.parse(seriesInfoStr) : getSeriesName(); + const episodeTitleText = getEpisodeTitle() || queue[idx].title; + const fullEpisodeTitle = `${seriesInfo.seriesName} ${episodeTitleText}`; + await downloadImagesAsZip(imageUrls, seriesInfo, fullEpisodeTitle, queue[idx].number); } else { log(`이미지 없음 - 스킵`); }