Greasy Fork

来自缓存

Greasy Fork is available in English.

小说阅读器

强制绕过网站防嵌套限制,在任意网页小窗口看小说。带老板键(Alt+M)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         小说阅读器
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  强制绕过网站防嵌套限制,在任意网页小窗口看小说。带老板键(Alt+M)
// @author       Gemini
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function() {
    'use strict';

    if (window.top !== window.self) return; // 防止嵌套套娃

    // 创建主容器
    const container = document.createElement('div');
    container.id = 'moyu-container';
    container.style.cssText = `
        position: fixed;
        top: 100px;
        right: 20px;
        width: 350px;
        height: 500px;
        background: white;
        border: 1px solid #ccc;
        box-shadow: 0 4px 12px rgba(0,0,0,0.15);
        z-index: 2147483647;
        display: none;
        flex-direction: column;
        resize: both;
        overflow: hidden;
        opacity: 0.85;
        font-family: sans-serif;
        border-radius: 6px;
    `;

    // 顶部控制栏
    const header = document.createElement('div');
    header.style.cssText = `
        padding: 6px;
        background: #f8f9fa;
        border-bottom: 1px solid #e9ecef;
        display: flex;
        gap: 5px;
        align-items: center;
    `;

    const urlInput = document.createElement('input');
    urlInput.type = 'text';
    urlInput.placeholder = '输入小说网址...';
    urlInput.style.cssText = 'flex-grow: 1; padding: 4px; font-size: 12px; border: 1px solid #ccc; border-radius: 3px;';
    urlInput.value = GM_getValue('moyu_last_url', '');

    const loadBtn = document.createElement('button');
    loadBtn.textContent = '加载';
    loadBtn.style.cssText = 'font-size: 12px; padding: 4px 8px; cursor: pointer; border: 1px solid #ccc; background: white; border-radius: 3px;';

    const opacityInput = document.createElement('input');
    opacityInput.type = 'range';
    opacityInput.min = '0.1';
    opacityInput.max = '1';
    opacityInput.step = '0.05';
    opacityInput.value = '0.85';
    opacityInput.style.cssText = 'width: 60px; cursor: pointer;';

    header.appendChild(urlInput);
    header.appendChild(loadBtn);
    header.appendChild(opacityInput);

    // 内容展示区 (使用 srcdoc 强行渲染)
    const iframe = document.createElement('iframe');
    iframe.style.cssText = `
        width: 100%;
        flex-grow: 1;
        border: none;
        background: #fff;
    `;

    container.appendChild(header);
    container.appendChild(iframe);
    document.body.appendChild(container);

    // 核心抓取逻辑 (绕过限制)
    const loadUrl = () => {
        let url = urlInput.value.trim();
        if (!url) return;
        if (!url.startsWith('http')) url = 'https://' + url;

        loadBtn.textContent = '加载中...';
        
        // 使用油猴特权跨域请求
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(response) {
                loadBtn.textContent = '加载';
                if(response.status !== 200) {
                    iframe.srcdoc = `<div style="padding:20px;color:red;">加载失败,对方服务器返回: ${response.status}</div>`;
                    return;
                }

                let html = response.responseText;

                // 1. 注入 base 标签,修复网页里的 CSS 和图片路径
                const baseTag = `<base href="${url}">`;
                if (html.includes('<head>')) {
                    html = html.replace('<head>', `<head>${baseTag}`);
                } else {
                    html = baseTag + html;
                }

                // 2. 拦截网页里的所有点击事件,点击下一章时由我们的脚本代为拉取,防止跳出
                const injectScript = `
                    <script>
                        document.addEventListener('click', function(e) {
                            const a = e.target.closest('a');
                            if (a && a.href && !a.href.startsWith('javascript:')) {
                                e.preventDefault(); // 阻止默认跳转
                                window.parent.postMessage({ type: 'MOYU_NAVIGATE', url: a.href }, '*');
                            }
                        });
                    </script>
                `;
                html += injectScript;

                // 强行把抓回来的代码塞进窗口
                iframe.srcdoc = html;
                GM_setValue('moyu_last_url', url);
            },
            onerror: function() {
                loadBtn.textContent = '加载';
                iframe.srcdoc = `<div style="padding:20px;color:red;">请求出错:可能是网络问题或跨域极度严格。</div>`;
            }
        });
    };

    loadBtn.addEventListener('click', loadUrl);
    urlInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') loadUrl(); });
    opacityInput.addEventListener('input', (e) => { container.style.opacity = e.target.value; });

    // 接收内部跳转消息
    window.addEventListener('message', (e) => {
        if (e.data && e.data.type === 'MOYU_NAVIGATE') {
            urlInput.value = e.data.url;
            loadUrl();
        }
    });

    // 老板键
    document.addEventListener('keydown', (e) => {
        if (e.altKey && e.key.toLowerCase() === 'm') {
            container.style.display = container.style.display === 'none' ? 'flex' : 'none';
            if (container.style.display === 'flex' && urlInput.value && !iframe.srcdoc) {
                loadUrl();
            }
        }
    });
})();