泛采专业版插件

泛采系统增强工具 - 提供主题切换、快捷键、自动刷新等功能,让您的工作更高效

// ==UserScript==
// @name        泛采专业版插件
// @name:en     FanCai Pro Plugin
// @description 泛采系统增强工具 - 提供主题切换、快捷键、自动刷新等功能,让您的工作更高效
// @description:en Enhanced toolset for FanCai System - Theme switching, keyboard shortcuts, auto-refresh and more
// @namespace   https://www.valuesimplex.com/
// @version     0.0.19
// @author      fangtiansheng <[email protected]>
// @icon        https://www.valuesimplex.com/images/favicon.ico
// @license     MIT
// @match       *://*/*
// @connect     *
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/axiosGmxhrAdapter.min.js
// @grant       GM.xmlHttpRequest
// @grant       GM.registerMenuCommand
// @grant       GM.unregisterMenuCommand
// @run-at      document-end
// ==/UserScript==

/*
 * 泛采专业版插件
 * ==============
 *
 * 功能特性:
 * 1. 主题切换 - 支持明暗主题自动切换
 * 2. 快捷键支持:
 *    - ESC: 关闭弹窗
 *    - Ctrl+S: 保存
 *    - 自定义快捷键绑定
 * 3. 自动刷新功能
 * 4. 面板切换优化
 * 5. 验证器位置调整
 * 6. 任务列表限制优化
 * 7. 安全特性检查
 *
 * 安装说明:
 * 1. 确保已安装支持用户脚本的浏览器扩展(如Tampermonkey)
 * 2. 点击安装链接即可自动安装
 *
 * 使用说明:
 * - 脚本会自动在泛采系统页面启用
 * - 可通过浏览器扩展的设置面板配置脚本选项
 * - 问题反馈请访问GitHub仓库提交Issue
 *
 * 更新日志:
 * v0.0.18
 * - 新增主题自动切换功能
 * - 优化键盘快捷键支持
 * - 提升性能和稳定性
 */

// 配置项
const CONFIG = {
    REFRESH_INTERVAL: 1000,
    THEME_CHECK_INTERVAL: 1000,
    MENU_ITEMS: [
        {
            name: '财经转到泛采',
            fn: toCrawl,
            accessKey: 'f'
        }
    ]
};

// 工具函数
const utils = {
    async get(url, config = {}) {
        return axios.get(url, {
            adapter: axiosGmxhrAdapter,
            ...config
        });
    },

    async post(url, data, config = {}) {
        return axios.post(url, data, {
            adapter: axiosGmxhrAdapter,
            ...config
        });
    },

    isCrawlPage() {
        return window.location.href.endsWith('#/home/crawl');
    }
};

// 核心功能
class CrawlerTools {
    constructor() {
        this.init();
    }

    async init() {
        // 从UserScript元信息中获取版本号
        const version = GM.info.script.version;
        console.log(`%c 泛采系统专业版插件 %c v${version} %c`,
            "background:#5D5D5D; padding:1px; border-radius:3px 0 0 3px; color:#fff",
            "background:#0D7FBF; padding:1px; border-radius:0 3px 3px 0; color:#fff",
            "background:transparent"
        );

        if (window.location.hash.includes('/home/crawl')) {
            // 爬虫管理页面特定功能
            this.bindKeyboardShortcuts();
            this.initThemeSwitch();
            this.initPanelSwitch();
            this.initAutoRefresh();
            this.initValidatorPosition();
            this.initTaskListLimit();
            console.log('爬虫管理页面功能已初始化');
        } else {
            // 在其他页面执行的功能
            this.checkSecurityFeatures();
            this.checkMetaTags();
            console.log('安全特性检查已初始化');
        }
    }

    bindKeyboardShortcuts() {
    // ESC关闭弹窗
        window.addEventListener("keydown", (e) => {
            if (e.key === "Escape") {
                const popups = Array.from(document.getElementsByClassName("popup_hover")).reverse();
                for (const popup of popups) {
                    if (popup.style.display === "") {
                        popup.querySelector(".popup_head_close_icon")?.click();
                        break;
                    }
                }
            }

            // Ctrl+S保存
            if (e.ctrlKey && e.key === "s") {
                e.preventDefault();
                const saveButton = document.querySelector(".pageDetail_toolBox_item, .planDetail_toolBox_item");
                const buttons = saveButton?.children;
                if (buttons?.length) {
                    buttons[buttons.length - 2].click();
                }
            }
        });
    }

    initThemeSwitch() {
        const head = document.querySelector(".head");
        if (!head) {
            console.log("未找到.head元素,等待DOM加载...");
            setTimeout(() => this.initThemeSwitch(), 1000);
            return;
        }

        // 检查是否已经存在主题切换按钮
        if (document.querySelector('[role="switch_theme_option"]')) {
            console.log("主题切换按钮已存在");
            return;
        }

        const switchHtml = `
            <label for="switch_theme_option" class="nav" style="color:#fff; position:absolute; right:400px;">亮色</label>
            <div class="el-form-item__content" style="margin-left:15px; margin-bottom:2px; position:absolute; right:350px;">
                <div role="switch_theme_option" aria-checked="true" class="el-switch is-checked">
                    <input type="checkbox" name="" true-value="true" class="el-switch__input">
                    <span class="el-switch__core" style="width:40px;"></span>
                </div>
            </div>
        `;

        const switchWrapper = document.createElement("div");
        switchWrapper.innerHTML = switchHtml;

        // 确保switchWrapper的内容被正确插入
        const fragment = document.createDocumentFragment();
        while (switchWrapper.firstChild) {
            fragment.appendChild(switchWrapper.firstChild);
        }
        head.appendChild(fragment);

        const switchElement = document.querySelector('[role="switch_theme_option"]');
        if (switchElement) {
            switchElement.addEventListener("click", this.toggleTheme.bind(this));
            console.log("主题切换按钮已添加并绑定事件");
        } else {
            console.log("未能找到主题切换按钮元素");
        }
    }

    toggleTheme(e) {
        const isDark = e.currentTarget.classList.contains("is-checked");
        e.currentTarget.classList.toggle("is-checked");

        const html = document.querySelector("html");
        const label = document.querySelector('label[for="switch_theme_option"]');

        if (isDark) {
            html.style = `mix-blend-mode:difference; filter:invert(80%) hue-rotate(0deg) brightness(110%);`;
            ['.cell', 'span', '.iconfont'].forEach(selector => {
                document.querySelectorAll(selector).forEach(el => el.style.color = '#111');
            });
            label.innerText = "暗色";
        } else {
            html.style = "";
            ['.cell', 'span', '.iconfont'].forEach(selector => {
                document.querySelectorAll(selector).forEach(el => el.style.color = '');
            });
            label.innerText = "亮色";
        }
    }

    initPanelSwitch() {
        if (!window.location.hash.includes('/home/crawl')) return;

        const checkAndInitSwitch = () => {
            const head = document.querySelector(".head");
            const button_switch = document.querySelector('div[role="switch_option"]');
            const label_switch = document.querySelector('label[for="switch_option"]');
            const main_box = document.querySelector(".pageDetail_mainBox");

            if (main_box && !button_switch && head) {
                const switch_option = document.createElement("div");
                head.insertBefore(switch_option, head.lastChild);
                switch_option.outerHTML = `
                    <label for="switch_option" class="nav" style="color: #fff;position: absolute;right: 300px;">显示</label>
                    <div class="el-form-item__content" style="margin-left: 15px; margin-bottom: 2px;position: absolute;right: 250px;">
                        <div role="switch_option" aria-checked="true" class="el-switch is-checked">
                            <input type="checkbox" name="" true-value="true" class="el-switch__input">
                            <span class="el-switch__core" style="width: 40px;"></span>
                        </div>
                    </div>
                `;

                const button_switch = document.querySelector('div[role="switch_option"]');
                if (button_switch) {
                    button_switch.addEventListener("click", this.handlePanelSwitch.bind(this));
                }
            }

            if (!main_box && button_switch) {
                button_switch.remove();
                label_switch?.remove();
            }
        };

        // 初始检查并每秒更新
        checkAndInitSwitch();
        setInterval(checkAndInitSwitch, 1000);
    }

    handlePanelSwitch(e) {
        const main_box = document.querySelector(".pageDetail_mainBox");
        const label = document.querySelector('label[for="switch_option"]');
        if (!main_box || !label) return;

        const isChecked = e.currentTarget.classList.contains("is-checked");
        e.currentTarget.classList.toggle("is-checked");

        const display = isChecked ? "none" : "block";
        const text = isChecked ? "隐藏" : "显示";

        // 更新所有子元素的显示状态
        Array.from(main_box.children)
            .slice(0, -2)
            .forEach(child => child.style.display = display);

        label.innerText = text;
    }

    async checkSecurityFeatures() {
        // 检查 Cloudflare
        try {
            const res = await fetch(location.origin + "/cdn-cgi/trace", {
                method: "HEAD",
                mode: "cors",
                credentials: "include"
            });
            if (res.ok) {
                console.log("网站使用了Cloudflare CDN服务");
            }
        } catch (e) {}

        // 检查加速乐
        if (document.cookie.includes('__jsl_clearance_s')) {
            console.log('网站启用了加速乐');
        }

        // 检查robots.txt
        try {
            const robotsRes = await utils.get("/robots.txt");
            if (robotsRes.status === 200) {
                console.log(`发现网站有robots.txt文件: ${robotsRes.request.responseURL}`);
                document.title = "" + document.title;
            }
        } catch (e) {}

        // 检查sitemap.xml
        try {
            const sitemapRes = await utils.get("/sitemap.xml");
            if (sitemapRes.status === 200) {
                console.log(`发现网站有sitemap.xml文件: ${sitemapRes.request.responseURL}`);
                document.title = "" + document.title;
            }
        } catch (e) {}
    }

    initAutoRefresh() {
        setInterval(async () => {
            try {
                const res = await utils.get("/crawl/crawl/get-user-list");
                if (res.status === 200) {
                    document.querySelectorAll(".head").forEach(head => {
                        head.style.backgroundColor = "";
                    });
                    document.title = "爬虫管理系统";
                }

                // 更新描述时间戳
                const descTextarea = document.querySelector('textarea');
                if (descTextarea) {
                    const timestamp = new Date().toLocaleString('sv-SE').replaceAll("/", "-") + '\n';
                    const timePattern = /\d+\-\d+\-\d+\s\d+:\d+:\d+\n/;
                    descTextarea.value = timePattern.test(descTextarea.value)
                        ? descTextarea.value.replace(timePattern, timestamp)
                        : timestamp + descTextarea.value;
                }
            } catch (e) {
                console.error("自动刷新失败:", e);
            }
        }, CONFIG.REFRESH_INTERVAL * 60);
    }

    initValidatorPosition() {
        setInterval(() => {
            const validatorDiv = document.querySelector('.crawl_validatorTable');
            if (validatorDiv && validatorDiv.parentNode.firstChild !== validatorDiv) {
                validatorDiv.parentNode.insertBefore(validatorDiv, validatorDiv.parentNode.firstChild);
            }
        }, CONFIG.REFRESH_INTERVAL);
    }

    // 修改任务列表数量限制
    initTaskListLimit() {
        // 保存原始的XMLHttpRequest.open方法
        const originalOpen = XMLHttpRequest.prototype.open;

        // 重写XMLHttpRequest.open方法
        XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
            // 检查URL是否匹配特定模式
            if (url.includes("/crawl/crawl/plan-task-select/") && url.includes("limit=10")) {
                // 将limit参数从10修改为100
                url = url.replace("limit=10", "limit=100");
            }

            // 使用修改后的URL调用原始的open方法
            originalOpen.call(this, method, url, async, user, password);
        };
    }

    // 检测页面meta标签
    checkMetaTags() {
        const title = window.document.title;
        const metaTags = {
            "ArticleTitle": './/*[@name="ArticleTitle"]/@content',
            "PubDate": './/*[translate(@name, "PUBDATE", "pubdate")="pubdate" and contains(@content, "20")]/@content',
            "ColumnName": './/*[translate(@name, "COLUMNNAME", "columnname")="columnname"]/@content',
            "ContentSource": './/*[translate(@name, "CONTENTSOURCE", "contentsource")="contentsource"]/@content',
            "Author": './/*[translate(@name, "AUTHOR", "author")="author"]/@content',
            "Keywords": './/*[translate(@name, "KEYWORDS", "keywords")="keywords"]/@content'
        };

        // 检查常规meta标签
        for (const [tag, xpath] of Object.entries(metaTags)) {
            const elements = document.getElementsByName(tag) || document.getElementsByName(tag.toLowerCase());
            if (elements.length || (tag === "PubDate" && document.getElementsByName("pubdate").length)) {
                console.log(xpath);
                if (tag === "ArticleTitle") {
                    window.document.title = "❤️" + window.document.title;
                    this.startTitleAnimation("❤️", "🔔", title, 600);
                }
            }
        }

        // 检查WordPress日期meta标签
        const metaPubDates = document.querySelectorAll('meta[content^="202"]');
        if (metaPubDates.length > 0) {
            metaPubDates.forEach((meta, index) => {
                console.log(`带有时间格式的meta标签${index + 1}:`, meta);

                if (meta.property?.startsWith("article:")) {
                    window.document.title = "💚" + window.document.title;
                    this.startTitleAnimation("💚", "🔔", title, 600);
                    console.log('.//*[contains(@property, "article:published")]/@content');
                }
            });
        }
    }

    // 标题动画辅助函数
    startTitleAnimation(emoji1, emoji2, baseTitle, interval) {
        let isFirstEmoji = true;
        const updateTitle = () => {
            window.document.title = isFirstEmoji ? emoji1 + baseTitle : emoji2 + baseTitle;
            isFirstEmoji = !isFirstEmoji;
        };

        updateTitle(); // 立即执行一次
        return setInterval(updateTitle, interval);
    }
}

// 初始化
async function toCrawl() {
    window.location = window.location.origin + "/#/home/crawl";
}

// 启动脚本
(async function main() {
    try {
        new CrawlerTools();
        CONFIG.MENU_ITEMS.forEach(item => {
            GM.registerMenuCommand(item.name, item.fn, item.accessKey);
        });
    } catch (e) {
        console.error("脚本初始化失败:", e);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址