kone
sub_icon

ainovel

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

2025-11-12 12:28:28
정보
조회 1914 · 좋아요 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
💥 26년 1분기, [AINOVEL] 순위 발표 (일부 줄거리 포함)
정보
코네
04.01 24251 59
53dushu에서 파일 찾으시는 분은
정보
naxlamas
03.11 10382 26
누더기 3.2 기반 번역 프롬프트
정보
greener-straper
02.09 9703 15
중국소설공유방 링크
정보
1atchup98fy
02.06 11019 21
패러디 본것들 주관적인 평점겸 작품 추천 구걸
정보
아스파가
01.03 9126 16
3.0 flash 누락 많으시면 프롬프트로 존나게 가스라이팅 돌리세요
정보
naxlamas
25.12.26 9595 21
고유명사 찐빠 거르는 프롬프트 연구
정보
greener-straper
25.12.18 9985 17
콜로모 프롬프트 공유
정보
Sorkim
25.12.08 12137 15
69shuba 탬퍼몽키 업데이트
정보
kone1133
25.11.26 8267 17
gemini blocked contents. 금지 컨텐츠 번역하는 프롬프트
정보
kone1133
25.11.25 4374 20
지금 내가 쓰는 배치번역기 잼민이 2.5pro용 프롬프트
정보
naxlamas
25.11.23 7311 28
코딩을 아예 몰라도 코딩을 하는 방법 with Gemini. 이미지 多
정보
74839
25.11.11 5248 23
101kanshu 소설 받기 스크립트(수정)
정보
kone1133
25.11.10 5997 19
제가 쓰는 검수용 파이썬 스크립트.
정보
kone1133
25.11.03 2998 20
!!! ㅡ Ai번역 너도 할 수 있다 (하는법)ㅡ !!!
정보
Ghost
25.11.02 8684 34
중국 소설 txt 다운로드 가능한 곳.
정보
cncjs2222
25.10.27 3889 15
사이버펑크2071,2075 용어집
정보
kone1133
25.10.21 4135 15
중국 소설 사이트 모음
정보
naxlamas
25.09.30 13184 41
AI소설 갤에 올리는 소설 번역 하는 법 정리
정보
dlatldyd123
25.09.10 8610 30