Stage1st论坛回复优化

在S1论坛回复框添加生成按钮

// ==UserScript==
// @name         Stage1st论坛回复优化
// @license MIT
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  在S1论坛回复框添加生成按钮
// @author       AnchorCat
// @match        *.saraba1st.com/2b/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      api.siliconflow.cn
// ==/UserScript==

// 硅基流动API配置
const API_KEY = '###'; // 请替换为实际API密钥
const API_URL = 'https://api.siliconflow.cn/v1/chat/completions';
const API_MODEL = 'Pro/deepseek-ai/DeepSeek-R1'; // 模型
//const API_MODEL = 'Pro/deepseek-ai/DeepSeek-V3';

(function() {
    'use strict';

    // 等待回复框加载完成
    const waitForReplyBox = (replyBoxame) => {
        return new Promise(resolve => {
            const checkInterval = setInterval(() => {
                const replyBox = document.querySelector(`#${replyBoxame}`);
                if (replyBox) {
                    clearInterval(checkInterval);
                    resolve(replyBox);
                }
            }, 100);
        });
    };

    // type:"diss" diss某一层说的话
    const generateS1StyleReply = async (text, type) => {
        var content = `你需要根据以下内容,将内容改写为符合Stage1st论坛风格的回复内容(你只需要生成回复内容即可),并且生成的内容和原内容观点保持一致:${text}`;
        if (type === "diss") {
            content = `你需要根据以下内容,将内容改写为符合Stage1st论坛风格的回复内容(你只需要生成回复内容即可),并且生成的内容要反对原内容观点:${text}`;
        }

        const messages = [
            {
                role: "system",
                content: "你作为一个深谙 Stage1st 论坛外野板块风格的回复者,熟练运用 [f:001] 此类格式的麻将脸表情包(数字可替换,三位数,不足三位数前面用 0 补足),禁止使用黄豆表情包、AC 娘表情包及其他表情包。给出的回复简洁明了,以巨魔语气进行回复,毫无做作之感。只在必要的时候加入赛博朋克,量子力学科技内容。"
            },
            {
                role: "user",
                content: content
            }
        ];

        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "POST",
                url: API_URL,
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${API_KEY}`
                },
                data: JSON.stringify({
                    model: API_MODEL,
                    messages: messages,
                    temperature: 1,
                    max_tokens: 4096
                }),
                onload: function(response) {
                    const data = JSON.parse(response.responseText);
                    resolve(data.choices[0].message.content);
                },
                onerror: reject
            });
        });
    };

    // 主逻辑
    const main = async () => {
        const replyBox = await waitForReplyBox("fastpostmessage");
        const btnContainer = replyBox.parentElement;

        // 添加生成按钮
        const genButton = document.createElement('button');
        genButton.textContent = `生成S1式回复【${API_MODEL}】`;
        genButton.style.cssText = `
            margin-left: 10px;
            background: #f90;
            color: white;
            border: none;
            padding: 5px 10px;
            cursor: pointer;
        `;

        genButton.addEventListener('click', async (e) => {
            e.stopPropagation(); // 阻止事件冒泡
            e.preventDefault(); // 禁用默认行为
            const originalText = replyBox.value;
            const locker = new ReplyLocker(replyBox);
            locker.lock();
            try {
                const generatedText = await generateS1StyleReply(originalText);
                replyBox.value = generatedText;
            } catch (error) {
                replyBox.value = locker.originalContent; // 恢复原始内容
                console.log("请求失败:", error);
            } finally {
                locker.unlock();
            }
        });
        btnContainer.appendChild(genButton);
    };
    main();

    // 动态监测器
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length) {
                attachButtonsToAreaInputs();
            }
        });
    });

    // 启动观察
    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: false,
        characterData: false
    });

    // 增强型元素绑定
    let isProcessing = false;
    const attachButtonsToAreaInputs = async () => {
        if (isProcessing) return;
        const replyBox = await waitForReplyBox("postmessage");
        const existingBtn = replyBox.parentElement.querySelector("#s1-ai-button-v2");
        if (existingBtn) return;
        isProcessing = true;

        // 添加生成按钮
        const genButton = document.createElement('button');
        genButton.textContent = `生成S1式回复【${API_MODEL}】`;
        genButton.id = "s1-ai-button-v2";
        genButton.style.cssText = `
            margin-left: 10px;
            background: #f90;
            color: white;
            border: none;
            padding: 5px 10px;
            cursor: pointer;
             `;

        replyBox.parentElement.insertBefore(genButton, replyBox.nextSibling);
        genButton.addEventListener('click', async (e) => {
            e.stopPropagation(); // 阻止事件冒泡
            e.preventDefault(); // 禁用默认行为
            const originalText = replyBox.value;
            const locker = new ReplyLocker(replyBox);
            locker.lock();
            try {
                const generatedText = await generateS1StyleReply(originalText);
                replyBox.value = generatedText;
            } catch (error) {
                replyBox.value = locker.originalContent; // 恢复原始内容
                console.log("请求失败:", error);
            } finally {
                locker.unlock();
            }
        });
        isProcessing = false;
    };

    const attachDissButtonsToReply = async () => {
        document.querySelectorAll('.fastre').forEach(fastre => {
            // 创建带有平台模型信息的按钮[^5]
            const dissBtn = document.createElement('button');
            dissBtn.textContent = `DISS它!【${API_MODEL}】`;
            // 插入到fastre元素之后
            fastre.parentNode.insertBefore(dissBtn, fastre.nextSibling);

            dissBtn.addEventListener('click', async (e) => {
                e.stopPropagation(); // 阻止事件冒泡
                e.preventDefault(); // 禁用默认行为
                const originalText = fastre.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".t_f").innerHTML;
                console.log("开始diss:", originalText);
                dissBtn.textContent = `正在DISS中!请稍后`;
                try {
                    const generatedText = await generateS1StyleReply(originalText, "diss");
                    // 找到回复按钮并模拟点击
                    const replyBtn = fastre.parentNode.parentNode.querySelector('a.fastre[onclick*="showWindow"]');
                    replyBtn.click(); // 🤖 触发点击事件,弹出窗口
                    // 点击后检查弹窗是否出现
                    setTimeout(() => {
                        autoFillReply(generatedText);
                    }, 1000); // ⏱️ 根据网络延迟调整延迟时间
                    console.log("diss成功:", generatedText);
                } catch (error) {
                    console.log("diss失败:", error);
                } finally {
                    console.log("diss结束:");
                    dissBtn.textContent = `DISS它!【${API_MODEL}】`;
                }
            });
        });
    };

    function autoFillReply(generatedText, retryCount = 0) {
        const replyInput = document.querySelector('#postmessage');
        if (replyInput) {
            replyInput.value = generatedText;
            console.log('成功填充回复内容喵~');
        } else if (retryCount < 2) { // 因为初始是0次,所以需要重试时次数是<2
            console.warn(`第 ${retryCount+1} 次寻找失败,1秒后重试...`);
            setTimeout(() => {
                autoFillReply(generatedText, retryCount + 1);
            }, 1000);
        } else {
            console.error('三次尝试后仍找不到回复框,停止重试喵~ (>﹏<)');
        }
    }

    // 首次加载执行
    attachButtonsToAreaInputs();
    attachDissButtonsToReply();
})();

GM_addStyle(`
    .s1-generating-tip {
        color: #666;
        font-size: 12px;
        padding: 8px;
        margin: 5px 0;
        background: #f9f9f9;
        border-left: 3px solid #f90;
    }
    /* 锁定状态下的回复框样式 */
    .s1-locked {
        opacity: 0.8;
        background-color: #f6f6f6 !important;
        cursor: not-allowed;
    }
`);

class ReplyLocker {
    constructor(replyBox) {
        this.replyBox = replyBox;
        this.originalContent = replyBox.value;
        this.originalStyle = replyBox.style.cssText;
    }

    lock() {
        // 锁定输入但允许复制
        this.replyBox.readOnly = true;
        this.replyBox.style.cssText = `
            background: #f0f0f0;
            opacity: 0.9;
            cursor: not-allowed;
            ${this.originalStyle}
        `;
        this.replyBox.parentElement.insertAdjacentHTML('beforeend',
                                                       `<div class="s1-generating-tip">【回复优化中】当前回复内容已锁定(大概需要30s)</div>`);
    }

    unlock() {
        this.replyBox.readOnly = false;
        this.replyBox.style.cssText = this.originalStyle;
        document.querySelector('.s1-generating-tip')?.remove();
    }
}