Greasy Fork

Greasy Fork is available in English.

大模型多站点

提高效率

当前为 2025-06-01 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         大模型多站点
// @namespace    http://tampermonkey.net/
// @version      1.1.5
// @description  提高效率
// @author       wz
// @match        https://www.kimi.com/*
// @match        https://chat.deepseek.com/*
// @match        https://www.tongyi.com/*
// @match        https://chatgpt.com/*
// @match        https://www.doubao.com/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @license      GPL-3.0-only

// ==/UserScript==

(function () {
    'use strict';
    console.log("ai script, start");

    const T = "tool-";
    const QUEUE = "tool-queue";
    const LEN = "len";
    const LAST_Q = "lastQ";
    const UID_KEY = "uid";
    const SPLIT_CHAR = ",,,";

    const MAX_QUEUE = 3;
    const checkGap = 100;
    let maxRetries = 100; // 最多尝试10秒

    let MAIN_SITE = 0;
    let site = 0;
    let url = window.location.href;

    const keywords = {
        "kimi": 0,
        "deepseek": 1,
        "tongyi": 2,
        "chatgpt": 3,
        "doubao": 4
    };
    for (const keyword in keywords) {
        if (url.indexOf(keyword) > -1) {
            site = keywords[keyword];
            break;
        }
    }

    const historySites = {
        0: "https://www.kimi.com/chat/",
        1: "https://chat.deepseek.com/a/chat/s/",
        2: "https://www.tongyi.com/?sessionId=",
        3: "https://chatgpt.com/c/",
        4: "https://www.doubao.com/chat/"
    }
    const newSites = {
        0: "https://www.kimi.com/",
        1: "https://chat.deepseek.com/",
        2: "https://www.tongyi.com/",
        3: "https://chatgpt.com/",
        4: "https://www.doubao.com/chat"
    }

    function getChatId(){
        let url = getUrl();
        let subStr = url.substring(url.lastIndexOf('/') + 1);
        // console.log("subStr: "+subStr);
        if(isEmpty(subStr)){
            return "";
        }
        if(site === 2){
            let mark = 'sessionId=';
            if(url.indexOf(mark) === -1){
                return "";
            }
            let tmp = url.lastIndexOf(mark) + mark.length;
            return url.substring(tmp);
        }else if(site === 3){
            if(subStr.indexOf("auto") > -1){
                return "";
            }
            return subStr;
        }else if(site === 4){
            if(subStr.indexOf("local") > -1){
                return "";
            }
            return subStr;
        }else{
            return subStr;
        }
    }

    function getUrl(){
        return window.location.href;
    }

    // 队列头部添加元素
    function enqueue(element) {
        let queue = JSON.parse(localStorage.getItem(QUEUE) || "[]");
        if (queue.length > 0 && queue[0] === element) {
            return;
        }
        queue.unshift(element);
        localStorage.setItem(QUEUE, JSON.stringify(queue));
    }

    // 当队列长度超过阈值,删除队尾元素
    function dequeue() {
        let queue = JSON.parse(localStorage.getItem(QUEUE) || "[]");
        let len = queue.length;
        if(len > MAX_QUEUE){

            let chatIdKey = T + queue[len - 1];
            let valJson = JSON.parse(getS(chatIdKey));
            if(!isEmpty(valJson)){
                let uid = valJson.uid;
                localStorage.removeItem("uid-" + uid);
                GM_deleteValue(uid);
            }

            localStorage.removeItem(chatIdKey);
            queue.pop();
            localStorage.setItem(QUEUE, JSON.stringify(queue));
        }
    }

    function hgetS(key, jsonKey){
        let json = localStorage.getItem(key);
        if(isEmpty(json)){
            return "";
        }
        json = JSON.parse(json);
        return json[jsonKey];

    }

    function hsetS(key, jsonKey, val){
        let json = JSON.parse(localStorage.getItem(key) || "{}");
        json[jsonKey] = val;
        localStorage.setItem(key, JSON.stringify(json));
    }

    function getS(key){
        return localStorage.getItem(key);
    }
    function setS(key, val){
        localStorage.setItem(key, val);
    }
    function setGV(key, value){
        GM_setValue(key, value);
    }
    function getGV(key){
        return GM_getValue(key);
    }


    let hasChatId = false;
    let lock = false;

    // setInterval(function(){
    //     masterCheckNew();
    //     receiveNew();
    // }, 3000);

    setTimeout(function(){
        setInterval(function(){
            masterCheckNew();
            receiveNew();
        }, 1000);

    }, 100);

    function getQuestionList(){
        let questions = [];
        if(site == 0){
            questions = document.getElementsByClassName("user-content");
        }else if(site === 1){
            let scrollable = document.getElementsByClassName("scrollable")[1];
            if(!isEmpty(scrollable)){
                let list = scrollable.firstElementChild.firstElementChild.children
                let elementsArray = Array.from(list);
                questions = elementsArray.filter((item, index) => index % 2 === 0);
            }
        }else if(site === 2){
            questions = document.querySelectorAll('[class^="bubble-"]');
        }else if(site === 3){
            questions = document.querySelectorAll('[data-message-author-role="user"]');
        }else if(site === 4){
            let list = document.querySelectorAll('[data-testid="message_text_content"]');
            let elementsArray = Array.from(list);
            questions = elementsArray.filter((item, index) => index % 2 === 0);
        }
        return questions;
    }

    // 发送端
    function masterCheckNew(){
        if(lock){
            return;
        }
        let masterId = getChatId();
        if(isEmpty(masterId)){
            return;
        }

        let questions = getQuestionList();
        let lenNext = questions.length;
        if(lenNext > 0){
            let len = hgetS(T + masterId, LEN) || 0;
            // console.log("lenNext: "+lenNext+", len: "+len);
            if(lenNext - len === 1){
                let lastestQ = questions[lenNext - 1].textContent;
                let lastQuestion = hgetS(T + masterId, LAST_Q);
                if(!isEmpty(lastQuestion) && lastestQ === lastQuestion){
                    return;
                }
                masterReq(masterId, lastestQ);
                hasChatId = true;
                hsetS(T + masterId, LEN, lenNext);
            }
        }
    };

    function masterReq(masterId, lastestQ){
        let uid = hgetS(T + masterId, UID_KEY);
        if(isEmpty(uid)){
            uid = guid();
            hsetS(T + masterId, UID_KEY, uid);
        }

        let message = {
            uid: uid,
            question: lastestQ
        };
        console.log(message);
        setGV("msg", message);
        hsetS(T + masterId, LAST_Q, lastestQ);

        let uidJson = getGV(uid);
        // 若json非空,则其中一定有首次提问的主节点的信息;
        // 故json若空则必为首次,只有首次会走如下逻辑
        if(isEmpty(uidJson)){
            uidJson = {};
            uidJson[site] = masterId;
            console.log("master print uidJson: "+JSON.stringify(uidJson));
            setGV(uid, uidJson);

            // 存储管理(删除与添加)
            dequeue();
            enqueue(masterId);
        }
    }

    function receiveNew(){
        let curSlaveId = getChatId();
        if(curSlaveId.length < 12){
            curSlaveId = "";
        }

        let msg = getGV("msg");
        if(isEmpty(msg)){
            return;
        }

        if(lock){
            return;
        }

        let question = msg.question;
        let lastQuestion = hgetS(T + curSlaveId, LAST_Q);

        let sameQuestion = !isEmpty(lastQuestion) && question === lastQuestion;
        console.log("question: "+question+", lastQuestion: "+lastQuestion);
        if(sameQuestion){
            return;
        }

        let questionBeforeJump = getS("questionBeforeJump");

        // 如果是经跳转而来,无需处理主节点信息,直接从缓存取对话内容
        if(!isEmpty(questionBeforeJump)){
            console.log("questionBeforeJump: " + questionBeforeJump);
            let splits = questionBeforeJump.split(SPLIT_CHAR);
            let cachedQuestion = splits[0];
            let cachedUid = splits[1];

            let cachedSlaveId = "";
            if(!isEmpty(curSlaveId)){
                cachedSlaveId = splits[2];
                if(curSlaveId !== cachedSlaveId){
                    return;
                }
                hsetS(T + curSlaveId, LAST_Q, cachedQuestion);
            }

            // 清空跳转用的缓存
            setS("questionBeforeJump", "");
            console.log("h1 send");
            abstractSend(cachedQuestion, cachedSlaveId);

            if(isEmpty(curSlaveId)){
                setUid(cachedUid, cachedQuestion);
            }
            return;
        }


        let uid = msg.uid;

        // 当前空,且之前chatId有值,则认为是手动打开的页面(若是从节点跟随跳转新页面的情况,前面已经拦截处理了)
        if(isEmpty(curSlaveId)){
            if(hasChatId){
                return;
            }
        }else{
            hasChatId = true;
        }

        let targetUrl = "";
        let slaveIdFlag = false;
        let slaveId = "";
        let uidJson = getGV(uid);
        let lastQuestionOfComingSlaveId = "";

        // 来者消息的uid,是否关联了从节点的chatId?
        if(!isEmpty(uidJson)){
            console.log("uidJson " + JSON.stringify(uidJson));
            slaveId = uidJson[site];
            lastQuestionOfComingSlaveId = hgetS(T + slaveId, LAST_Q);
            console.log("lastQuestionOfComingSlaveId "+lastQuestionOfComingSlaveId);
            if(question === lastQuestionOfComingSlaveId){
                return;
            }
            if(!isEmpty(slaveId)){
                slaveIdFlag = true;
            }
        }

        let curIdFlag = !isEmpty(curSlaveId);
        // 从节点已进行过来者的uid对应的对话
        if(slaveIdFlag){
            // 当前页面有chatId
            if(curIdFlag){
                // chatId相同则对话,不同则跳转
                if(curSlaveId === slaveId){
                    if(!sameQuestion){
                        hsetS(T + curSlaveId, LAST_Q, question);
                        console.log("h2 send");
                        abstractSend(question, curSlaveId);
                    }
                }else{
                    targetUrl = historySites[site] + slaveId;
                }
            // 当前页面是空白,需跳转
            }else{
                targetUrl = historySites[site] + slaveId;
            }
        // 对从节点而言是新对话
        }else{
            // 当前页面有chatId,则跳转空白页
            if(curIdFlag){
                targetUrl = newSites[site];
            // 当前页面已经是空白页
            }else{
                console.log("h3 send");
                abstractSend(question, "");
                setUid(uid, question);
            }
        }
        if(!isEmpty(targetUrl)){
            setS("questionBeforeJump", question + SPLIT_CHAR + uid + SPLIT_CHAR + slaveId);
            window.location.href = targetUrl;
        }
    }

    function setUid(uid, question){
        let intervalId;
        let lastUrl = getUrl();
        let count = 0;
        let waitTime = 15000;
        if(site === 3){
            waitTime *= 2;
        }

        console.log("ready to setUid");
        intervalId = setInterval(function() {
            count ++;
            if(count > waitTime / checkGap){
                console.log("setUid超时");
                clearInterval(intervalId);
            }
            let chatId = getChatId();
            if (!isEmpty(chatId)) {
                hasChatId = true;

                let uidJson = getGV(uid);
                if(!isEmpty(uidJson)){
                    if(isEmpty(uidJson[site])){
                        uidJson[site] = chatId;
                    }
                }else{
                    uidJson = {};
                    uidJson[site] = chatId;
                }
                hsetS(T + chatId, LAST_Q, question);
                hsetS(T + chatId, LEN, 1);

                console.log("slave print uidJson: "+JSON.stringify(uidJson));
                setGV(uid, uidJson);
                setS("uid-" + uid, JSON.stringify(uidJson));

                lock = false;
                console.log("setUid finish");
                hsetS(T + chatId, UID_KEY, uid);

                // 存储管理(删除与添加)
                dequeue();
                enqueue(chatId);

                clearInterval(intervalId);
            }
        }, checkGap);
    }

    // ① 检查textArea存在 ② 检查sendBtn存在 ③ 检查问题列表长度是否加一
    function abstractSend(content, chatId){
        let intervalId;
        let count = 0;

        lock = true;

        intervalId = setInterval(function() {
            count ++;
            if(count > 5000 / checkGap){
                clearInterval(intervalId);
            }
            const textarea = getTextArea(site);
            if (!isEmpty(textarea)) {
                clearInterval(intervalId);
                sendContent(textarea, content, chatId);
            }
        }, checkGap);
    }

    function sendContent(textarea, content, chatId){
        textarea.focus();
        document.execCommand('insertText', false, content);
        clickAndCheckLen(chatId);
    }

    function clickAndCheckLen(chatId) {
        let tryCount = 0;

        const checkBtnInterval = setInterval(() => {
            let quesFlag = false;
            if(isEmpty(chatId)){
               quesFlag = true;
            }else{
                let len = getQuestionList().length;
                if(len > 0){
                    quesFlag = true;
                }
            }

            let sendBtn = getBtn(site);
            if (quesFlag && !isEmpty(sendBtn)) {
                clearInterval(checkBtnInterval);
                setTimeout(function(){
                    // sendBtn存在不一定立即可以点击,最好延迟一下
                    sendBtn.click();
                }, 200);
                checkQuesList(chatId);
            } else {
                tryCount++;
                if (tryCount > maxRetries) {
                    clearInterval(checkBtnInterval);
                    console.log("tryCount "+tryCount + ", quesFlag "+quesFlag+", sendBtn "+isEmpty(sendBtn));
                    console.warn("sendBtn或问题列表未找到,超时");
                    return;
                }
            }
        }, checkGap);
    }

    function checkQuesList(chatId) {
        let tryCount = 0;
        let cachedLen = hgetS(T + chatId, LEN);
        let newFlag = isEmpty(chatId) || isEmpty(cachedLen) || cachedLen === 0;

        const checkInterval = setInterval(() => {
            tryCount++;

            // 定时器:检查问题列表长度大于上次,则停止,并设置lock
            // 注意,若是chat首个问题,则只要求len=1
            let len = getQuestionList().length;
            let questionDisplayFlag = false;
            if(newFlag){
                if(len === 1){
                    questionDisplayFlag = true;
                }
            }else{
                if(len > cachedLen){
                    questionDisplayFlag = true;
                }
            }

            if (questionDisplayFlag) {
                clearInterval(checkInterval);
                if(!isEmpty(chatId)){
                    hsetS(T + chatId, LEN, len);
                    lock = false; // 解锁(如果chatId空,有setUid方法负责解锁)
                }
            } else if (tryCount > maxRetries) {
                console.log("tryCount "+tryCount + ", len "+len+", cachedLen "+cachedLen+", newFlag "+newFlag);
                clearInterval(checkInterval);
                console.warn("问题列表长度未符合判据,超时");
                lock = false;
            }
        }, checkGap);
    }


	function getTextArea(site){
        if(site == 0){
            return document.getElementsByClassName('chat-input-editor')[0];
        }else if(site === 1){
            return document.getElementById('chat-input');
        }else if([2, 4].includes(site)){
            return document.getElementsByTagName('textarea')[0];
        }else if(site === 3){
            return document.getElementById('prompt-textarea');
        }
	}
	function getBtn(site){
        if(site == 0){
            return document.getElementsByClassName('send-button')[0];
        }else if(site === 1){
            var btns = document.querySelectorAll('[role="button"]');
            return btns[btns.length - 1];
        }else if(site === 2){
            return document.querySelectorAll('[class^="operateBtn-"], [class*=" operateBtn-"]')[0];
        }else if(site === 3){
            return document.getElementById('composer-submit-button');
        }else if(site === 4){
            return document.getElementById('flow-end-msg-send');
        }
	}

    function isEmpty(item){
        if(item===null || item===undefined || item.length===0 || item === "null"){
            return true;
        }else{
            return false;
        }
    }

    function guid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

})();