kone
sub_icon

ainovel

Xbanxia 소설 다운로드 스크립트 템퍼멍키

2025-11-12 12:28:28
정보
조회 1922 · 좋아요 8

// ==UserScript== // @name Xbanxia Novel Downloader (Background Fetch) // @namespace http://tampermonkey.net/ // @version 1.2 // UI 크기 및 체크박스 미세 조정 // @description Downloads novel text from xbanxia.cc by fetching chapters in the background. Handles rate-limiting (429) and forbidden (403) errors with retries. // @author You // @match https://www.xbanxia.cc/books/* // @grant GM_download // @grant GM_xmlhttpRequest // @require https://code.jquery.com/jquery-3.6.0.min.js // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // ======================================================================= // 1. 선택자 및 상수 // ======================================================================= const episodeListSelector = '.book-list ul li a'; const textToRemove = ''; let isCheckAll = false; let countLabel; // 다운로드 제어 상수 const BASE_DELAY_MS = 1500; const INITIAL_PAGE_DELAY_MS = 500; const MAX_RETRIES = 5; const MIN_FILE_SIZE_KB = 1; function log(message) { console.log(`[Tampermonkey: Xbanxia Downloader] ${message}`); } // ======================================================================= // 2. UI 및 보조 함수 (CSS 수정 포함) // ======================================================================= function addGlobalStyle() { if ($('#tampermonkey-shuba-style').length > 0) return; const style = document.createElement('style'); style.id = 'tampermonkey-shuba-style'; style.innerHTML = ` /* UI 컨트롤 요소 기본 스타일 */ .tampermonkey-ui-element { display: inline-block; margin-left: 5px; vertical-align: middle; } /* 중앙 상단 카운트 레이블 (크기 증가 및 Z-INDEX 강화) */ #tampermonkey-count-label { position: fixed !important; top: 10px !important; left: 50% !important; transform: translateX(-50%) !important; z-index: 99999 !important; background-color: #007bff !important; color: white !important; padding: 10px 15px !important; /* 패딩 증가 */ border-radius: 4px !important; font-family: sans-serif !important; font-size: 16px !important; /* 폰트 크기 증가 */ font-weight: bold !important; /* 폰트 굵게 */ box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important; pointer-events: none !important; min-width: 150px !important; /* 최소 너비 확보 */ text-align: center !important; } /* 컨트롤 컨테이너 */ #tampermonkey-controls-container { margin-bottom: 10px; padding: 10px; background-color: #f0f0f0; border-radius: 5px; display: flex; align-items: center; flex-wrap: wrap; gap: 5px; } /* 하단 다운로드 버튼 */ #tampermonkey-download-button-bottom { position: fixed !important; bottom: 20px !important; left: 50% !important; transform: translateX(-50%) !important; z-index: 9998 !important; background-color: #28a745 !important; border: none !important; color: white !important; padding: 15px 32px !important; text-align: center !important; font-size: 16px !important; cursor: pointer !important; border-radius: 8px !important; } #tampermonkey-download-button-bottom:disabled, #tampermonkey-download-button-top:disabled { background-color: #cccccc !important; cursor: not-allowed !important; } /* 챕터 리스트 레이아웃 수정 */ .book-list ul li { display: flex !important; align-items: center !important; line-height: normal !important; height: auto !important; } /* 체크박스 미세 조정: 아래로 1px 이동 */ .book-list ul li input[type="checkbox"] { flex-shrink: 0 !important; margin-top: 1px !important; /* 1px 아래로 이동 */ } .book-list ul li a { white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; } `; document.head.appendChild(style); } function updateCheckedCount() { if (!countLabel) return; const checkedCount = $(episodeListSelector).parent().find('input[type="checkbox"]:checked').length; countLabel.text(`선택된 항목 = ${checkedCount}개`); } function addCheckboxes() { let episodeIndex = 1; $(episodeListSelector).each(function() { const link = $(this); const listItem = link.parent(); if (listItem.find('input[type="checkbox"]').length > 0) return; const checkbox = $('').css('margin-right', '5px'); checkbox.on('change', updateCheckedCount); const chapterUrl = new URL(link.attr('href'), window.location.href).href; link.data({ index: episodeIndex++, title: link.text().trim(), url: chapterUrl }); listItem.prepend(checkbox); }); updateCheckedCount(); } function createListPageUI() { if ($('#tampermonkey-controls-container').length > 0) return; addGlobalStyle(); countLabel = $('').attr('id', 'tampermonkey-count-label'); $('body').append(countLabel); const checkAllButton = $('모두 선택').addClass('tampermonkey-ui-element'); const uncheckAllButton = $('모두 해제').addClass('tampermonkey-ui-element'); const downloadButton = $('선택 다운로드').attr('id', 'tampermonkey-download-button-top').addClass('tampermonkey-ui-element'); const rangeInput = $('').addClass('tampermonkey-ui-element'); const checkTBButton = $('범위 선택').addClass('tampermonkey-ui-element'); const uncheckTBButton = $('범위 해제').addClass('tampermonkey-ui-element'); const betweenChkButton = $('선택 사이').addClass('tampermonkey-ui-element'); checkAllButton.on('click', function() { isCheckAll = !isCheckAll; $(episodeListSelector).parent().find('input[type="checkbox"]').prop('checked', isCheckAll); $(this).text(isCheckAll ? '모두 해제' : '모두 선택'); updateCheckedCount(); }); uncheckAllButton.on('click', function() { $(episodeListSelector).parent().find('input[type="checkbox"]').prop('checked', false); isCheckAll = false; checkAllButton.text('모두 선택'); updateCheckedCount(); }); downloadButton.on('click', startBackgroundDownload); function handleRangeAction(shouldCheck, start, end) { if (start === undefined || end === undefined) { const rangeString = rangeInput.val(); const range = rangeString.split('-').map(s => parseInt(s.trim(), 10)); if (range.length < 1 || isNaN(range[0])) return; start = range.length > 1 && !isNaN(range[1]) ? Math.min(...range) : range[0]; end = range.length > 1 && !isNaN(range[1]) ? Math.max(...range) : range[0]; } $(episodeListSelector).filter((i, el) => { const index = $(el).data('index'); return index >= start && index <= end; }).parent().find('input[type="checkbox"]').prop('checked', shouldCheck); updateCheckedCount(); } checkTBButton.on('click', () => handleRangeAction(true)); uncheckTBButton.on('click', () => handleRangeAction(false)); betweenChkButton.on('click', function() { const checkedIndexes = $(episodeListSelector).parent().find('input:checked').siblings('a').map((i, el) => $(el).data('index')).get(); if (checkedIndexes.length < 2) { alert("범위를 지정하려면 최소 두 개의 체크박스를 선택해주세요."); return; } handleRangeAction(true, Math.min(...checkedIndexes), Math.max(...checkedIndexes)); }); const controlsContainer = $('


').attr('id', 'tampermonkey-controls-container'); controlsContainer.append(checkAllButton, uncheckAllButton, downloadButton, rangeInput, checkTBButton, uncheckTBButton, betweenChkButton); const insertionPoint = $('h2#tithook'); if (insertionPoint.length) { controlsContainer.insertBefore(insertionPoint); } else { $('body').prepend(controlsContainer); } const downloadButtonBottom = $('선택 다운로드').attr('id', 'tampermonkey-download-button-bottom'); downloadButtonBottom.on('click', startBackgroundDownload); $('body').append(downloadButtonBottom); } /** * 본문 파싱 함수 (xbanxia.cc) */ function parseRawHtml(rawHtml) { const $tempDiv = $('').html(rawHtml); const chapterTitle = $tempDiv.find('h1#nr_title').text().trim(); const $txtContent = $tempDiv.find('#nr1'); if ($txtContent.length === 0) { log("Error: '#nr1' area not found in fetched HTML."); return `\n\n${chapterTitle}\n\n[오류: 본문 영역을 찾을 수 없음]`; } $txtContent.find('script, .outbt, .ccdd, .nav2, .comments-area, #comment, #comments, #submit, #author').remove(); let chapterContent = $txtContent.html(); chapterContent = chapterContent.replace(//ig, '\n'); chapterContent = chapterContent.replace(/ /g, ' '); chapterContent = chapterContent.replace(/[\u2003\u3000]/g, ' '); chapterContent = chapterContent.replace(/\s*本站無彈出廣告\s*/g, ''); chapterContent = chapterContent.replace(/<[^>]*>/g, ''); chapterContent = chapterContent.split('\n').map(line => line.trim()).join('\n'); chapterContent = chapterContent.replace(/(\n\s*){3,}/g, '\n\n'); chapterContent = chapterContent.trim(); return `${chapterTitle}\n\n${chapterContent}`; } function startBackgroundDownload() { const chaptersQueue = $(episodeListSelector).parent().find('input:checked').siblings('a').map((i, el) => { return { ...$(el).data(), retryCount: 0 }; }).get(); if (chaptersQueue.length === 0) return alert("다운로드할 에피소드를 선택해주세요."); let processedCount = 0; const totalCount = chaptersQueue.length; const downloadButtons = $('#tampermonkey-download-button-top, #tampermonkey-download-button-bottom'); downloadButtons.prop('disabled', true); function processNextChapter() { if (chaptersQueue.length === 0) { log("All chapters processed."); downloadButtons.prop('disabled', false).text('다운로드 완료!'); setTimeout(() => downloadButtons.text('선택 다운로드'), 5000); return; } const chapter = chaptersQueue.shift(); const currentProgress = totalCount - chaptersQueue.length; downloadButtons.text(`다운로드 중... (${currentProgress}/${totalCount}${chapter.retryCount > 0 ? ` (재시도 ${chapter.retryCount}/${MAX_RETRIES})` : ''})`); const refererUrl = chapter.url; GM_xmlhttpRequest({ method: "GET", url: chapter.url, headers: { "Referer": refererUrl, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }, onload: function(response) { let delay = BASE_DELAY_MS; let needsRetry = false; let chapterContent = ''; let errorContent = ''; const filename = `${chapter.index}.${chapter.title.replace(/[<>:"/\\|?*]/g, '_')}.txt`; if (response.status === 200) { chapterContent = parseRawHtml(response.responseText); const cleanedContent = chapterContent.replace(new RegExp(textToRemove.replace(/⊥/g, '⊥?'), 'g'), "").replace(/[\uFEFF]/g, '').trim(); if (cleanedContent.length < (MIN_FILE_SIZE_KB * 500)) { errorContent = `[본문 내용이 너무 짧음 (${cleanedContent.length}자), 재시도]`; needsRetry = true; } else { processedCount++; log(`[${processedCount}/${totalCount}] Fetched ${chapter.title} successfully.`); GM_download({ url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(cleanedContent), name: filename, onload: () => log(`Successfully triggered download for: ${filename}`), onerror: (err) => log(`Error downloading ${filename}: ${JSON.stringify(err)}`) }); } } else if (response.status === 429 || response.status === 403) { errorContent = `[다운로드 실패: HTTP 상태 코드 ${response.status}]`; needsRetry = true; } else { processedCount++; log(`Failed to fetch ${chapter.title}, Status: ${response.status}`); errorContent = `[다운로드 실패: HTTP 상태 코드 ${response.status}]`; GM_download({ url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(errorContent), name: filename }); } if (needsRetry) { if (chapter.retryCount < MAX_RETRIES) { chapter.retryCount++; delay = BASE_DELAY_MS * Math.pow(2, chapter.retryCount); if (response.status === 429 && response.responseHeaders) { const retryAfterHeaderMatch = response.responseHeaders.match(/Retry-After: (\d+)/i); if (retryAfterHeaderMatch) { const serverDelay = parseInt(retryAfterHeaderMatch[1], 10) * 1000; delay = Math.max(delay, serverDelay); } } log(`[RETRY] ${errorContent} for ${chapter.title}. Retrying in ${delay / 1000}s. Attempt ${chapter.retryCount}/${MAX_RETRIES}`); chaptersQueue.unshift(chapter); } else { processedCount++; log(`[FATAL] Max retries reached for ${chapter.title}. Skipping.`); const finalErrorContent = `[다운로드 실패: ${errorContent}, 최대 재시도 횟수 ${MAX_RETRIES}회 초과]`; GM_download({ url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(finalErrorContent), name: filename }); } } setTimeout(processNextChapter, delay); }, onerror: function(error) { processedCount++; log(`Error fetching ${chapter.title} (Network Error): ${JSON.stringify(error)}`); setTimeout(processNextChapter, BASE_DELAY_MS); } }); } log(`Starting background download for ${totalCount} individual chapters with ${BASE_DELAY_MS/1000}s base delay.`); setTimeout(() => { processNextChapter(); }, INITIAL_PAGE_DELAY_MS); } // ======================================================================= // 5. 초기화 // ======================================================================= function initializeUI() { addCheckboxes(); createListPageUI(); } $(document).ready(function() { if (window.location.href.includes('/books/') && window.location.href.endsWith('.html')) { initializeUI(); } }); })();


아래 요청한 소설,

60년도로 회귀 부자되고 아이키우기 소설...

검색해보니 이 사이트가 나와서 일단 올립니다.

링크:

https://www.xbanxia.cc/books/79393.html


重回六零:種田發家養崽崽

8
댓글 2개
댓글 쓰기
25.11.12
대단하십니다
25.11.12
잘 모르겠지만 저기는 언정소설 전문 사이트 인 듯.
0 / 500
코드기어스/기어스 세계에 환생했더니 병약한 일본인 여자애였다 후기
후기
zxcvbb89
05.25 1339 3
[주술회전] 저주받은 검 제작자 189 咒回:鑄劍師開局 주술회전 파트 완료, 무직전생 파트 진행 중.
[패러디]
rlawjrwnd
05.25 2723 23
테라에 왔는데, 읽을 책이 없다 1-1356/테라에서, 냉혹하고 무자비한 자로서 살아가다 2권 180화
[패러디]
명일방주너무좋아광인
05.25 1435 32
[패러디]나루토 이 우치하는 정의롭다 못해 사악해 후기
후기
74839
05.25 1940 6
나는 거품경제 도쿄에서 만화를 그린다 후기
후기
ddt1412
05.25 805 4
MD 파일은 뭔가요?
일반
sankhara
05.25 772 1
나루토 닌계 과학승리 진짜 얼탱이네요
후기
marvrl1020
05.25 2571 2
스포) 현환 시스템 환급 승급 이거 미친 전개네 ㅋㅋㅋㅋㅋ
후기
DDAT
05.25 1733 6
헌터 x 헌터 : 붉은 눈의 조르딕, 붉은 손과 파란 손으로 시작한 자[복구요청합니다!]
복구 요청
latee1
05.25 707 0
[유희왕] 유희왕 A5: SR(스피드 로이드) 테마로 시작! 1-89
[패러디]
金日成綜合大學
05.25 1622 17
테라기상천외소국 후기
잡담
wlrmaqnxj
05.24 763 0
유희왕은 낭만이 있다.
잡담
金日成綜合大學
05.24 863 1
임신 빌런들중이 가장 기억에 남았던 건
일반
시라카미 후부키
05.24 2132 2
[디지몬] 디지몬 - 오더 1-1227
[패러디]
金日成綜合大學
05.24 3145 66
디지몬 소설인데 검열 오류가 뜨다니
잡담
金日成綜合大學
05.24 843 0
중국인들 멘헤라 멘헤라네
일반
eratohofu
05.24 984 0
DnD류 추천부탁드립니다.
잡담
vango
05.24 1937 1
내가 제대로 된 모험가가 아니라고 누가 그래 하차후기
후기
darksun
05.24 1587 4
고진인(蛊真人) 1-2335 (AI 재번역판 손검수) 복구 요청 부탁드립니다
복구 요청
glagayayg
05.24 1564 2
쌀 학급 서바이벌:설산으로 전이했더니 내가 인간 핫팩이 되었다1~91
[일반]
nagaking366
05.24 3250 29
대체역사물 대문호 후기
후기
qqq2we123123
05.24 1270 5
ㅎ 접속이 안되는데요
잡담
cncjs2222
05.24 1025 -5
사스케가 나뭇잎을 탈주한 이유 2026 05 24
[일반]
金日成綜合大學
05.24 2622 49
혹시 중국에서 mygo가 굉장히 인기있는 이유좀 알려주싈분
일반
ny1215
05.24 810 0
헌터 붉은 눈의 조르딕, 붉은 손과 파란 손으로 시작한 자 - 지금까지 읽은 헌터 소설 중 최고
후기
tls10
05.24 1908 8
[AI 번역]『여문공단원들의 마지막 행방』(女文工团员最后的下落)
[야설]
greener-straper
05.24 2648 39
ㅎㄷㄷ... 임신 빌런
잡담
金日成綜合大學
05.24 2945 4
찾았다! 주력의 핵심을!
잡담
金日成綜合大學
05.24 994 8
[워해머] 밤의 주인 1-139
[패러디]
야돈
05.24 2854 38
[엘든링] 빛바랜 자는 가장 완벽한 엔딩을 원한다 1-603 (완)
[패러디]
야돈
05.24 1864 53