Greasy Fork

来自缓存

Greasy Fork is available in English.

Fanqie Novel Free Reading

番茄小说免费网页阅读 不用客户端 可下载小说

当前为 2025-03-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name              Fanqie Novel Free Reading
// @namespace         https://github.com/SmashPhoenix272
// @version           5.2
// @description       番茄小说免费网页阅读 不用客户端 可下载小说
// @description:zh-cn 番茄小说免费网页阅读 不用客户端 可下载小说
// @description:en    Fanqie Novel Reading, No Need for a Client, Novels Available for Download
// @author            ibxff, SmashPhoenix272
// @license           MIT License
// @match             https://fanqienovel.com/*
// @require           https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @icon              data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0zNS40Mjg2IDQuODg0MzVDMzkuNjQ2MyA0Ljg4NDM1IDQzLjA4MTYgOC4zMTk3MyA0My4wODE2IDEyLjUzNzRWMzUuNDI4NkM0My4wODE2IDM5LjY0NjMgMzkuNjQ2MyA0My4wODE2IDM1LjQyODYgNDMuMDgxNkgxMi41Mzc0QzguMzE5NzMgNDMuMDgxNiA0Ljg4NDM1IDM5LjY0NjMgNC44ODQzNSAzNS40Mjg2VjEyLjUzNzRDNC44ODQzNSA4LjMxOTczIDguMzE5NzMgNC44ODQzNSAxMi41Mzc0IDQuODg0MzVIMzUuNDI4NlpNMzUuNDI4NiA0SDEyLjUzNzRDNy44MDk1MiA0IDQgNy44MDk1MiA0IDEyLjUzNzRWMzUuNDI4NkM0IDQwLjE1NjUgNy44MDk1MiA0My45NjYgMTIuNTM3NCA0My45NjZIMzUuNDI4NkM0MC4xNTY1IDQzLjk2NiA0My45NjYgNDAuMTU2NSA0My45NjYgMzUuNDI4NlYxMi41Mzc0QzQ0IDcuODA5NTIgNDAuMTU2NSA0IDM1LjQyODYgNFoiIGZpbGw9IiMzMzMiLz48cGF0aCBkPSJNMjkuMTAxNiA0VjEyLjQwMTRMMzIuMzMyOSAxMC41NjQ2TDM1LjU2NDEgMTIuNDAxNFY0SDI5LjEwMTZaIiBmaWxsPSIjMzMzIi8+PHBhdGggZD0iTTI0LjAzNCAxOC4yODU4QzE1LjgzNjcgMTguMjg1OCA4LjU1NzgyIDIxLjg1NzIgNCAyNy4zNjc0VjM1LjQyODZDNCA0MC4xNTY1IDcuODA5NTIgNDMuOTY2IDEyLjUzNzQgNDMuOTY2SDM1LjQyODZDNDAuMTU2NSA0My45NjYgNDMuOTY2IDQwLjE1NjUgNDMuOTY2IDM1LjQyODZWMjcuMjY1NEMzOS40MDgyIDIxLjc4OTIgMzIuMTk3MyAxOC4yODU4IDI0LjAzNCAxOC4yODU4Wk0xNC42MTIyIDM3LjY3MzVDMTMuMTE1NiAzNy42NzM1IDEyLjQwMTQgMzcuMTI5MyAxMi40MDE0IDM2LjQxNUMxMi40MDE0IDM1LjcwMDcgMTMuMDgxNiAzNS4xMjI1IDE0LjU3ODIgMzUuMTIyNUMxNi4wNzQ4IDM1LjEyMjUgMTcuODc3NiAzNi4zODEgMTcuODc3NiAzNi4zODFDMTcuODc3NiAzNi4zODEgMTYuMTA4OCAzNy42NzM1IDE0LjYxMjIgMzcuNjczNVpNMTUuODM2NyAzMS4yMTA5QzE0Ljc0ODMgMzAuMTU2NSAxNC42NDYzIDI5LjI3MjIgMTUuMTU2NSAyOC43NjJDMTUuNjY2NyAyOC4yNTE4IDE2LjU1MSAyOC4zMTk4IDE3LjYzOTUgMjkuNDA4MkMxOC43Mjc5IDMwLjQ2MjYgMTkuMDY4IDMyLjYwNTUgMTkuMDY4IDMyLjYwNTVDMTkuMDY4IDMyLjYwNTUgMTYuODkxMiAzMi4yNjU0IDE1LjgzNjcgMzEuMjEwOVpNMjQuMDM0IDMwLjQ2MjZDMjQuMDM0IDMwLjQ2MjYgMjIuNzQxNSAyOC43Mjc5IDIyLjcwNzUgMjcuMTk3M0MyMi43MDc1IDI1LjcwMDcgMjMuMjUxNyAyNC45ODY0IDIzLjk2NiAyNC45ODY0QzI0LjY4MDMgMjQuOTg2NCAyNS4yNTg1IDI1LjY2NjcgMjUuMjU4NSAyNy4xNjMzQzI1LjI5MjUgMjguNjkzOSAyNC4wMzQgMzAuNDYyNiAyNC4wMzQgMzAuNDYyNlpNMzAuMzYwNSAyOS4zNzQyQzMxLjQ0OSAyOC4zMTk4IDMyLjMzMzMgMjguMjUxOCAzMi44NDM1IDI4LjcyNzlDMzMuMzUzNyAyOS4yMzgxIDMzLjI1MTcgMzAuMTIyNSAzMi4xNjMzIDMxLjE3NjlDMzEuMDc0OCAzMi4yMzEzIDI4LjkzMiAzMi41Mzc1IDI4LjkzMiAzMi41Mzc1QzI4LjkzMiAzMi41Mzc1IDI5LjI3MjEgMzAuNDI4NiAzMC4zNjA1IDI5LjM3NDJaTTMzLjM1MzcgMzcuNjczNUMzMS44NTcxIDM3LjY3MzUgMzAuMDg4NCAzNi4zNDcgMzAuMDg4NCAzNi4zNDdDMzAuMDg4NCAzNi4zNDcgMzEuODU3MSAzNS4wODg1IDMzLjM4NzggMzUuMDg4NUMzNC44ODQ0IDM1LjA4ODUgMzUuNTk4NiAzNS43MDA3IDM1LjU2NDYgMzYuMzgxQzM1LjU2NDYgMzcuMTI5MyAzNC44NTAzIDM3LjY3MzUgMzMuMzUzNyAzNy42NzM1WiIgZmlsbD0iIzMzMyIvPjwvc3ZnPg==
// @grant             GM_xmlhttpRequest
// @updateURL
// ==/UserScript==

const styleElement = document.createElement("style");
const cssRule = `
    @keyframes hideAnimation {
      0% {
        opacity: 1;
      }
      50% {
        opacity: 0.75;
      }
      100% {
        opacity: 0;
        display: none;
      }
    }

    option:checked {
        background-color: #ffb144;
        color: white;
    }
    `;

styleElement.innerHTML = cssRule;
document.head.appendChild(styleElement);

function hideElement(ele) {
  if (!ele) return;
  ele.style.animation = "hideAnimation 1.5s ease";
  ele.addEventListener("animationend", function () {
    ele.style.display = "none";
  });
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

const mark = (ele) => {
  if (ele) ele.style.boxShadow = "0px 0px 50px rgba(0, 0, 0, 0.2)";
};

// Function to extract content from different response formats
function extractContent(data) {
    if (data.code === 200 && data.data && data.data.content) {
        return data.data.content;
    }
    throw new Error('Unexpected response format');
}

(function() {
    'use strict';
    const path = window.location.href.match(/\/([^/]+)\/\d/)?.[1];

    switch(path){
        case 'reader':
            const toolbar = document.querySelector("#app > div > div > div > div.reader-toolbar > div > div.reader-toolbar-item.reader-toolbar-item-download");
            const text = toolbar?.querySelector('div:nth-child(2)');
            
            if (toolbar && text) {
                mark(toolbar);
                text.innerHTML = 'Processing';
            }

            document.title = document.title.replace(/在线免费阅读_番茄小说官网$/, '')

            var currentURL = window.location.href
            setInterval(() => window.location.href !== currentURL ? location.reload() : null, 1000);

            // Get content div for both PC and mobile layouts
            let cdiv;

            // Try PC layout first
            cdiv = document.getElementsByClassName('muye-reader-content noselect')[0];
            if (cdiv) {
                console.log('Found PC layout content div');
                cdiv.classList = cdiv.classList[0];
            } else {
                // Try mobile layout
                console.log('Trying mobile layout');
                const html0 = document.getElementById('html_0');
                if (!html0) {
                    console.log('Could not find html_0 for mobile layout');
                    return;
                }
                cdiv = html0.children[2] || html0.children[0];
                if (!cdiv) {
                    console.log('Could not find content div in mobile layout');
                    return;
                }
                console.log('Found mobile layout content div');
            }

            console.log('Content div found:', cdiv);

            console.log('Getting chapter ID...');
            const url = window.location.href;
            const regex = /\/(\d+)/;
            const match = url.match(regex);
            if (!match) {
                console.log('Could not extract chapter ID');
                return;
            }

            const extractedId = match[1];
            console.log('Chapter ID:', extractedId);
            const apiUrl = `https://api.cenguigui.cn/api/tomato/content.php?item_id=${extractedId}`;

            console.log('Making API request...');
            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const data = JSON.parse(response.responseText);
                            const content = extractContent(data);
                            console.log(content);
                            document.getElementsByClassName('muye-to-fanqie')[0]?.remove();
                            document.getElementsByClassName('pay-page')[0]?.remove();
                            cdiv.innerHTML = content.replace(/\n/g, "</p><p>").replace(/  /g,"").replace(/(.*?)\n/, '');

                            document.getElementsByClassName('muye-to-fanqie')[0]?.remove();
                            document.getElementsByClassName('pay-page')[0]?.remove();
                            cdiv.innerHTML = content.replace(/\n/g, "</p><p>").replace(/  /g,"").replace(/(.*?)\n/, '');

                            // Clean up any remaining pay-related elements
                            document.getElementById('html_0')?.classList.remove('pay-page-html');

                            if (toolbar && text) {
                                toolbar.style.backgroundColor = '#B0E57C'
                                text.innerHTML = 'Successed'
                                hideElement(toolbar)
                            }
                        } catch (error) {
                            console.error('Error processing content:', error);
                            if (toolbar && text) {
                                toolbar.style.backgroundColor = 'pink'
                                text.innerHTML = 'Failed'
                                hideElement(toolbar)
                            }
                        }
                    }
                },
                onerror: function(error) {
                    if (toolbar && text) {
                        toolbar.style.backgroundColor = 'pink'
                        text.innerHTML = 'Failed'
                        hideElement(toolbar)
                    }
                    console.error(`Fetch error: ${error}`);
                }
            });
     break;

     case 'page':
            const infoName = document.querySelector("#app > div > div.muye.muye-page > div > div.page-wrap > div > div.page-header-info > div.info > div.info-name > h1")?.innerHTML;
            const authorName = document.querySelector(".author-name-text")?.innerHTML;
            const totalChapters = document.querySelector(".page-directory-header h3")?.textContent.match(/(\d+)章/)?.[1] || '';
            const infoLabels = Array.from(document.querySelectorAll('.info-label span')).map(span => span.textContent).join(' ');
            const wordCount = document.querySelector('.info-count-word')?.textContent.trim();
            const lastUpdate = document.querySelector('.info-last')?.textContent.trim();
            const abstract = document.querySelector("#app > div > div.muye.muye-page > div > div.page-body-wrap > div > div.page-abstract-content > p")?.innerHTML;

            var content = 'Using Free Fanqie script download\n\n' +
                         '作者:' + authorName + '\n' +
                         '书名:' + infoName + '\n' +
                         '标签:' + infoLabels + '\n' +
                         '字数:' + wordCount + '\n' +
                         '更新:' + lastUpdate + '\n\n' +
                         '简介:' + abstract + '\n'
            content = content.replace(/undefined|null|NaN/g,'')

            const processContentForDownload = (content) => {
                return content
                    .replace(/<[^>]+>/g, '')  // Remove all HTML tags
                    .replace(/&nbsp;/g, ' ')  // Replace HTML spaces
                    .replace(/&lt;/g, '<')    // Replace HTML entities
                    .replace(/&gt;/g, '>')
                    .replace(/&amp;/g, '&')
                    .replace(/&quot;/g, '"')
                    .replace(/\n\s*\n/g, '\n')  // Remove multiple consecutive newlines
                    .split('\n')  // Split into paragraphs
                    .map(para => '  ' + para)  // Add 2 spaces to each paragraph
                    .join('\n')  // Join back together
                    .trim();
            };

            sleep(1500).then(()=>{
                document.querySelector("#app > div > div.muye.muye-page > div > div.page-wrap > div > div.page-header-info > div.info > div.download-icon.muyeicon-tomato")?.remove()
                const download = document.querySelector("#app > div > div.muye.muye-page > div > div.page-wrap > div > div.page-header-info > div.info > a")
                if (!download) return;
                
                const downloadSpan = download.querySelector('button > span')
                if (downloadSpan) downloadSpan.innerHTML = '*Download Novel'
                download.href = 'javascript:void 0'

                const parentElement = document.querySelector("#app > div > div.muye.muye-page > div > div.page-wrap > div > div.page-header-info > div.info");
                if (!parentElement) return;

                const selectElement = document.createElement("select");
                selectElement.className = "byte-btn byte-btn-primary byte-btn-size-large byte-btn-shape-square muye-button";
                const options = [
                    {text: "UTF-8" },
                    {text: "GBK" },
                    {text: "UNICODE" },
                    {text:'UTF-16'},
                    {text:'ASCII'}
                ];
                options.forEach(function(optionData) {
                    var option = document.createElement("option");
                    option.text = optionData.text;
                    option.value = optionData.text
                    selectElement.appendChild(option);
                });
                selectElement.style.position = "absolute";
                selectElement.style.left = "320px";
                selectElement.style.bottom = "0px";
                selectElement.style.height = "32px";
                selectElement.style.width = "80px";
                selectElement.style.fontSize = "15px";
                parentElement.appendChild(selectElement);

                const books = Array.from(document.getElementsByClassName('chapter-item'))
                var accomplish = false

                function next(){
                    if (!books.length) return;
                    const ele = books[0].querySelector('a')
                    if (!ele) return;

                    ele.style.border = "3px solid navajowhite"
                    ele.style.borderRadius = "5px"
                    ele.style.backgroundColor = "navajowhite"
                    const url = ele.href;
                    console.log(url)
                    const regex = /\/(\d+)/;
                    const match = url.match(regex);
                    if (!match) return;

                    const extractedId = match[1];
                    const apiURL = `https://api.cenguigui.cn/api/tomato/content.php?item_id=${extractedId}`;
                    const charset = selectElement.value

                    content += '\n\n'
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: apiURL,
                        'Content-Type': "application/json; charset="+charset,
                        onload: function(response) {
                            if (response.status === 200) {
                                try{
                                    const data = JSON.parse(response.responseText);
                                    const chapterContent = extractContent(data);
                                    const processedContent = processContentForDownload(chapterContent);
                                    content += processedContent;
                                    ele.style.backgroundColor = '#D2F9D1'
                                    ele.style.border = "2px solid #D2F9D1"
                                    books.shift()
                                    console.log(books)
                                    if(!books.length){
                                        console.log('Download succesfully, saving')
                                        console.log(charset)
                                        const blob = new Blob([new TextEncoder(charset).encode(content)], { type: `text/plain;charset=`+charset });
                                        const fileName = totalChapters ? `${infoName}_${totalChapters}章.txt` : `${infoName}.txt`;
                                        saveAs(blob, fileName);
                                        return
                                    }
                                    else{
                                        next()
                                    }
                                }
                                catch(e){
                                    ele.style.backgroundColor = 'pink'
                                    ele.style.border = "2px solid pink"
                                    next()
                                }
                            }
                        },
                        onerror: function(error) {
                            console.error(`Fetch error: ${error}`);
                            ele.style.backgroundColor = 'pink'
                            ele.style.border = "2px solid pink"
                            next()
                        },
                        ontimeout: function(error) {
                            console.error(`Fetch error: ${error}`);
                            ele.style.backgroundColor = 'pink'
                            ele.style.border = "2px solid pink"
                            next()
                        }
                    });
                }
                download.addEventListener('click', next)
                download.addEventListener('click', ()=>{
                    download.style.display = 'none';
                    selectElement.style.display = 'none'
                })
            })
      break;
    }
})();