您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从长佩(gongzicp.com)下载章节文本
- // ==UserScript==
- // @name Changpei Chapter Downloader
- // @name:zh-CN 长佩章节下载器
- // @namespace http://tampermonkey.net/
- // @version 0.2
- // @description Download chapter content from Changpei(gongzicp.com)
- // @description:zh-CN 从长佩(gongzicp.com)下载章节文本
- // @author oovz
- // @match *://*gongzicp.com/read-*.html
- // @grant none
- // @source https://gist.github.com/oovz/8c1c38607ed01cb594ebbd4913ff2c60
- // @source https://greasyfork.org/en/scripts/536172-changpei-chapter-downloader
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- // Configure your selectors here
- const APP_WRAPPER_SELECTOR = '#app'; // for MutationObserver
- const TITLE_SELECTOR = 'div.title > div.name'; // for title
- const CONTENT_SELECTOR = 'div.h-reader > div.content'; // for content
- const NEXT_CHAPTER_BASE_SELECTOR = 'div#readPage div.item > a'; // for next chaper link
- const NEXT_ICON_IDENTIFIER = 'ic_next'; // for next chapter icon
- const AUTHOR_SAY_SELECTOR = 'div.h-reader div.postscript > div.value'; // for author say
- // Internationalization
- const isZhCN = navigator.language.toLowerCase() === 'zh-cn' ||
- document.documentElement.lang.toLowerCase() === 'zh-cn';
- const i18n = {
- copyText: isZhCN ? '复制文本' : 'Copy Content',
- copiedText: isZhCN ? '已复制!' : 'Copied!',
- nextChapter: isZhCN ? '下一章' : 'Next Chapter',
- noNextChapter: isZhCN ? '没有下一章' : 'No Next Chapter',
- includeAuthorSay: isZhCN ? '包含作家说' : 'Include Author Say',
- excludeAuthorSay: isZhCN ? '排除作家说' : 'Exclude Author Say'
- };
- // State variable for author say inclusion
- let includeAuthorSay = true;
- // Create GUI elements
- const gui = document.createElement('div');
- gui.style.cssText = `
- position: fixed;
- bottom: 20px;
- right: 20px;
- background: white;
- padding: 15px;
- border: 1px solid #ccc;
- border-radius: 5px;
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
- z-index: 9999;
- resize: both;
- overflow: visible;
- min-width: 350px;
- min-height: 250px;
- max-width: 100vw;
- max-height: 80vh;
- resize-origin: top-left;
- display: flex;
- flex-direction: column;
- `;
- // Add CSS for custom resize handle at top-left
- const style = document.createElement('style');
- style.textContent = `
- @keyframes spin {
- to { transform: rotate(360deg); }
- }
- .resize-handle {
- position: absolute;
- width: 14px;
- height: 14px;
- top: 0;
- left: 0;
- cursor: nwse-resize;
- z-index: 10000;
- background-color: #888;
- border-top-left-radius: 5px;
- border-right: 1px solid #ccc;
- border-bottom: 1px solid #ccc;
- }
- .spinner-overlay {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(240, 240, 240, 0.8);
- display: none;
- justify-content: center;
- align-items: center;
- z-index: 10001;
- }
- `;
- document.head.appendChild(style);
- // Create resize handle
- const resizeHandle = document.createElement('div');
- resizeHandle.className = 'resize-handle';
- const output = document.createElement('textarea');
- output.style.cssText = `
- width: 100%;
- flex: 1;
- margin-bottom: 8px;
- resize: none;
- overflow: auto;
- box-sizing: border-box;
- min-height: 180px;
- `;
- output.readOnly = true;
- // Create button container for horizontal layout
- const buttonContainer = document.createElement('div');
- buttonContainer.style.cssText = `
- display: flex;
- justify-content: center;
- gap: 10px;
- margin-bottom: 2px;
- `;
- // Create toggle author say button
- const toggleAuthorSayButton = document.createElement('button');
- toggleAuthorSayButton.textContent = includeAuthorSay ? i18n.excludeAuthorSay : i18n.includeAuthorSay;
- toggleAuthorSayButton.style.cssText = `
- padding: 4px 12px;
- cursor: pointer;
- background-color: #fbbc05; /* Yellow */
- color: white;
- border: none;
- border-radius: 15px;
- font-weight: bold;
- font-size: 0.9em;
- `;
- const copyButton = document.createElement('button');
- copyButton.textContent = i18n.copyText;
- copyButton.style.cssText = `
- padding: 4px 12px;
- cursor: pointer;
- background-color: #4285f4;
- color: white;
- border: none;
- border-radius: 15px;
- font-weight: bold;
- font-size: 0.9em;
- `;
- // Create next chapter button
- const nextChapterButton = document.createElement('button');
- nextChapterButton.textContent = i18n.nextChapter;
- nextChapterButton.style.cssText = `
- padding: 4px 12px;
- cursor: pointer;
- background-color: #34a853;
- color: white;
- border: none;
- border-radius: 15px;
- font-weight: bold;
- font-size: 0.9em;
- `;
- // Add buttons to container
- buttonContainer.appendChild(toggleAuthorSayButton);
- buttonContainer.appendChild(copyButton);
- buttonContainer.appendChild(nextChapterButton);
- // Create spinner overlay for better positioning
- const spinnerOverlay = document.createElement('div');
- spinnerOverlay.className = 'spinner-overlay';
- // Create spinner
- const spinner = document.createElement('div');
- spinner.style.cssText = `
- width: 30px;
- height: 30px;
- border: 4px solid rgba(0,0,0,0.1);
- border-radius: 50%;
- border-top-color: #333;
- animation: spin 1s ease-in-out infinite;
- `;
- spinnerOverlay.appendChild(spinner);
- // Add elements to GUI
- gui.appendChild(resizeHandle);
- gui.appendChild(output);
- gui.appendChild(buttonContainer);
- gui.appendChild(spinnerOverlay);
- document.body.appendChild(gui);
- // Custom resize functionality
- let isResizing = false;
- let originalWidth, originalHeight, originalX, originalY;
- resizeHandle.addEventListener('mousedown', (e) => {
- e.preventDefault();
- isResizing = true;
- originalWidth = parseFloat(getComputedStyle(gui).width);
- originalHeight = parseFloat(getComputedStyle(gui).height);
- originalX = e.clientX;
- originalY = e.clientY;
- document.addEventListener('mousemove', resize);
- document.addEventListener('mouseup', stopResize);
- });
- function resize(e) {
- if (!isResizing) return;
- const width = originalWidth - (e.clientX - originalX);
- const height = originalHeight - (e.clientY - originalY);
- if (width > 300 && width < window.innerWidth * 0.8) {
- gui.style.width = width + 'px';
- // Keep right position fixed and adjust left position
- gui.style.right = getComputedStyle(gui).right;
- }
- if (height > 250 && height < window.innerHeight * 0.8) {
- gui.style.height = height + 'px';
- // Keep bottom position fixed and adjust top position
- gui.style.bottom = getComputedStyle(gui).bottom;
- }
- }
- function stopResize() {
- isResizing = false;
- document.removeEventListener('mousemove', resize);
- document.removeEventListener('mouseup', stopResize);
- }
- // Helper function to find the next chapter link
- function findNextChapterLink() {
- // Find all navigation links
- const navLinks = document.querySelectorAll(NEXT_CHAPTER_BASE_SELECTOR);
- console.log(`Found ${navLinks.length} navigation link candidates`);
- // Look for the link with the next chapter icon
- for (const link of navLinks) {
- const iconImg = link.querySelector('img.iconfont');
- if (iconImg) {
- console.log(`Found icon with src: ${iconImg.src}`);
- if (iconImg.src && iconImg.src.includes(NEXT_ICON_IDENTIFIER)) {
- console.log(`Found next chapter link: ${link.href}`);
- return link;
- }
- }
- }
- console.log('No next chapter link found');
- return null; // No next chapter link found
- }
- // Legacy XPath extraction function (kept for fallback compatibility)
- function getElementsByXpath(xpath) {
- const results = [];
- const query = document.evaluate(
- xpath,
- document,
- null,
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
- null
- );
- for (let i = 0; i < query.snapshotLength; i++) {
- const node = query.snapshotItem(i);
- if (node) {
- // Get full text content including children, preserving whitespace
- const textContent = node.textContent; // Keep original whitespace
- // Only push if the content is not just whitespace
- if (textContent && textContent.trim()) {
- results.push(textContent);
- }
- }
- }
- return results;
- }
- // Initial extraction
- function updateTitleOutput() {
- // Use querySelector with the new selector for title
- const titleElement = document.querySelector(TITLE_SELECTOR);
- if (titleElement) {
- // Extract direct text content, similar to XPath approach
- let directTextContent = '';
- for (let i = 0; i < titleElement.childNodes.length; i++) {
- const childNode = titleElement.childNodes[i];
- if (childNode.nodeType === Node.TEXT_NODE) {
- directTextContent += childNode.textContent;
- }
- }
- return directTextContent.trim();
- }
- return '';
- }
- function updateContentOutput(includeAuthorSayFlag) {
- // Use querySelector to get the content container
- const contentContainer = document.querySelector(CONTENT_SELECTOR);
- let elements = [];
- if (contentContainer) {
- // Get all p elements within the content container
- const paragraphs = contentContainer.querySelectorAll('p');
- // Process each paragraph, excluding those with the "watermark" class
- paragraphs.forEach(p => {
- // Skip paragraphs with the "watermark" class
- if (!p.classList.contains('watermark')) {
- const textContent = p.textContent;
- // Only add paragraphs that have non-whitespace content
- if (textContent && textContent.trim()) {
- elements.push(textContent);
- }
- }
- });
- }
- if (elements.length === 0) {
- console.error('no elements found for content, maybe using canvas');
- }
- // Join elements, do not trim here to preserve first line indentation
- let content = elements.join('\n');
- // Append author say if requested
- if (includeAuthorSayFlag) {
- // Use querySelector for author say with the new selector
- const authorSayElement = document.querySelector(AUTHOR_SAY_SELECTOR);
- let authorSayContent = '';
- if (authorSayElement) {
- authorSayContent = authorSayElement.textContent.trim();
- }
- // Add author say content if it exists
- if (authorSayContent) {
- // Add separation if both content and author say exist
- if (content.trim()) {
- content += '\n\n---\n\n' + authorSayContent; // Add separator
- } else {
- content = authorSayContent;
- }
- }
- }
- return content; // Return potentially leading-whitespace content
- }
- // Async update function
- async function updateOutput() {
- // Show spinner overlay
- spinnerOverlay.style.display = 'flex';
- // Use setTimeout to make it async and not block the UI
- setTimeout(() => {
- try {
- const title = updateTitleOutput();
- const content = updateContentOutput(includeAuthorSay); // Pass the state
- output.value = title ? title + '\n\n' + content : content;
- } catch (error) {
- console.error('Error updating output:', error);
- } finally {
- // Hide spinner when done
- spinnerOverlay.style.display = 'none';
- }
- }, 0);
- }
- // Run initial extraction
- updateOutput();
- // Add event listener for toggle author say button
- toggleAuthorSayButton.addEventListener('click', () => {
- includeAuthorSay = !includeAuthorSay; // Toggle state
- toggleAuthorSayButton.textContent = includeAuthorSay ? i18n.excludeAuthorSay : i18n.includeAuthorSay;
- updateOutput(); // Update the content
- });
- // Add event listener for copy button
- copyButton.addEventListener('click', () => {
- output.select();
- document.execCommand('copy');
- copyButton.textContent = i18n.copiedText;
- setTimeout(() => {
- copyButton.textContent = i18n.copyText;
- }, 1000);
- });
- // Add event listener for next chapter button
- nextChapterButton.addEventListener('click', () => {
- // Find the next chapter link using our helper function
- const nextChapterLink = findNextChapterLink();
- if (nextChapterLink) {
- // Navigate to the next chapter
- window.location.href = nextChapterLink.href;
- } else {
- // Show a message if there's no next chapter
- nextChapterButton.textContent = i18n.noNextChapter;
- nextChapterButton.style.backgroundColor = '#ea4335';
- setTimeout(() => {
- nextChapterButton.textContent = i18n.nextChapter;
- nextChapterButton.style.backgroundColor = '#34a853';
- }, 2000);
- }
- });
- // Find the content container element to observe (using the content selector)
- const contentElement = document.querySelector(APP_WRAPPER_SELECTOR);
- // Setup MutationObserver to watch for changes
- if (contentElement) {
- const observer = new MutationObserver(() => {
- updateOutput();
- });
- observer.observe(contentElement, {
- childList: true,
- subtree: true,
- characterData: true
- });
- // Also observe the document body for any structural changes that might affect the content
- observer.observe(document.body, {
- childList: true,
- subtree: false // Only direct children of body
- });
- } else {
- console.error('Content element not found. Cannot setup observer.');
- }
- })();