Zotero GPT Connector

Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm Yiyan Tongyi Claude Mytan ChanlderAi DeepSeek Doubao AIStudio

目前為 2024-10-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Zotero GPT Connector
// @description  Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm Yiyan Tongyi Claude Mytan ChanlderAi DeepSeek Doubao AIStudio
// @namespace    http://tampermonkey.net/
// @icon         https://github.com/MuiseDestiny/zotero-gpt/blob/bootstrap/addon/chrome/content/icons/favicon.png?raw=true
// @version      3.2.2
// @author       Polygon
// @match        https://chatgpt.com/*
// @match        https://gemini.google.com/app*
// @match        https://poe.com/*
// @match        https://www.coze.com/*
// @match        https://kimi.moonshot.cn/*
// @match        https://chatglm.cn/*
// @match        https://yiyan.baidu.com/*
// @match        https://tongyi.aliyun.com/*
// @match        https://qianwen.aliyun.com/*
// @match        https://claude.ai/*
// @match        https://mytan.maiseed.com.cn/*
// @match        https://mychandler.bet/*
// @match        https://chat.deepseek.com/*
// @match        https://www.doubao.com/chat/*
// @match        https://*.chatshare.biz/*
// @match        https://chat.kelaode.ai/*
// @match        https://chat.rawchat.cn/*
// @match        https://chat.sharedchat.top/*
// @match        https://node.dawuai.buzz/*
// @match        https://aistudio.google.com/*
// @match        https://claude.ai0.cn/*
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_cookie
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(async function () {
  'use strict';
  let isRunning = true
  let AI = "ChatGPT"
  const host = location.host
  if (host == 'chatgpt.com') {
    AI = "ChatGPT"
  } else if (host == 'gemini.google.com') {
    AI = "Gemini"
  } else if (host == 'poe.com') {
    AI = "Poe"
  } else if (host == 'kimi.moonshot.cn') {
    AI = "Kimi"
  } else if (host == 'www.coze.com') {
    AI = "Coze"
  } else if (host == "chatglm.cn") {
    localStorage.conversation_id = ""
    AI = "Chatglm"
  } else if (host == 'yiyan.baidu.com') {
    AI = "Yiyan"
  } else if (host == 'tongyi.aliyun.com' || host == 'qianwen.aliyun.com') {
    AI = "Tongyi"
  } else if (host == "claude.ai" || host == 'chat.kelaode.ai' || host.includes("claude")) {
    AI = "Claude"
  } else if (host == 'mytan.maiseed.com.cn') {
    AI = "MyTan"
  } else if (host == 'mychandler.bet') {
    localStorage.conversation_id = ""
    AI = "ChanlderAi"
  } else if (host == 'chat.deepseek.com') {
    AI = "DeepSeek"
  } else if (host == "www.doubao.com") {
    AI = "Doubao"
  } else if (host == 'aistudio.google.com') {
    AI = "AIStudio"
  }
  const requestPatchArr = [
    {
      AI: "Kimi",
      regex: /https:\/\/kimi.moonshot.cn\/api\/chat\/.+\/completion\/stream/,
      extract: function (text) {
        // console.log(text)
        for (let line of text.split("\n")) {
          if (line.startsWith("data")) {
            const data = JSON.parse(line.split("data: ")[1])
            if (data.event == "cmpl") {
              this.text += data.text
            }
          }
        }
      },
      text: ""
    },
    {
      AI: "AIStudio",
      regex: /GenerateContent$/,
      extract: function (text) {
        while (true) {
          try {
            JSON.parse(text)
            break
          } catch {
            text += "]"
          }
        }
        const data = JSON.parse(text)
        this.text += data[0].map(i => i[0][0][0][0][0][1]).join("")
      },
      text: ""
    },
    {
      AI: "ChatGPT",
      regex: /backend-api\/conversation$/,
      extract: function (text) {
        console.log("=>", text)
        for (let line of text.split("\n")) {
          if (line.startsWith('data: {"message')) {
            try { JSON.parse(line.split("data: ")[1]) } catch { continue }
            const data = JSON.parse(line.split("data: ")[1])
            if (data.message.content.content_type == "text") {
              this.text = data.message.content.parts[0]
            }
          } else if (line.startsWith("data: {")) {
            try { JSON.parse(line.split("data: ")[1]) } catch { continue }
            const data = JSON.parse(line.split("data: ")[1])
            if (data.p && data.p == "/message/content/parts/0" && this.text.length > 0) { this.text += "\n---\n" }
            if (typeof (data.v) == "string") {
              this.text += data.v
            } else if (data.v instanceof Array) {
              if (data.v[0].o == "append") {
                this.text += data.v[0].v
              }
            }
          }
        }
      },
      text: ""
    },
    {
      AI: "Claude",
      regex: /chat_conversations\/.+\/completion/,
      extract: function (text) {
        for (let line of text.split("\n")) {
          if (line.startsWith("data: {")) {
            try { JSON.parse(line.split("data: ")[1]) } catch { continue }
            const data = JSON.parse(line.split("data: ")[1])
            if (data.type && data.type == "completion") {
              this.text += data.completion
            }
          }
        }
      },
      text: ""
    }
  ]

  // 数据拦截,部分网站需要

  const originalXhrOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function (method, url, async) {
    this.addEventListener('readystatechange', async function () {
      let requestPatch
      if ((requestPatch = requestPatchArr.find(i => i.regex.test(url)))) {
        execInZotero(`
            let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
            task.responseText = ${JSON.stringify(requestPatch.text)};
            task.type = "pending";
            task.responseType = "markdown";
        `);
        if (this.readyState === 3) {
          requestPatch.text = ""
          // 请求返回数据的流式部分
          try {
            requestPatch.extract(this.responseText)
          } catch {
            console.log(this.responseText)
          }
          await execInZotero(`
              let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
              task.responseText = ${JSON.stringify(requestPatch.text)};
              task.type = "pending";
              task.responseType = "markdown"
            `)
        } else if (this.readyState == 0) {
          await execInZotero(`
            let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
            task.responseText = ${JSON.stringify(requestPatch.text)};
            task.type = "done";
            task.responseType = "markdown"
          `)
          requestPatch.text = ""
        }
      }
    });
    originalXhrOpen.apply(this, arguments);
  };


  const originalFetch = window.fetch;
  unsafeWindow.fetch = function () {
    return originalFetch.apply(this, arguments)
      .then(response => {
        const url = response.url
        const requestPatch = requestPatchArr.find(i => i.regex.test(url))
        if (requestPatch) {
          execInZotero(`
                let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
                task.responseText = ${JSON.stringify(requestPatch.text)};
                task.type = "pending";
                task.responseType = "markdown";
            `);
          const clonedResponse = response.clone();
          console.log("requestPatch", requestPatch)
          requestPatch.text = ""
          console.log(clonedResponse)
          const reader = clonedResponse.body.getReader();
          const decoder = new TextDecoder()
          let allText = ""
          function processStream() {
            reader.read().then(({ done, value }) => {
              if (done) {
                requestPatch.text = ""
                requestPatch.extract(allText)
                execInZotero(`
                    let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
                    task.responseText = ${JSON.stringify(requestPatch.text)};
                    task.type = "done";
                    task.responseType = "markdown";
                `);
                return;
              }

              // 将 Uint8Array 转为字符串
              const text = decoder.decode(value, { stream: true });
              allText += text
              try {
                requestPatch.extract(text)
              } catch {}

              execInZotero(`
                  let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
                  task.responseText = ${JSON.stringify(requestPatch.text)};
                  task.type = "pending";
                  task.responseType = "markdown";
              `);

              // 递归调用,继续读取流数据
              processStream();
            }).catch(error => {
              // 捕获所有错误,包括 AbortError
              console.log("Error when Patch", error)
              requestPatch.text = ""
              requestPatch.extract(allText)
              execInZotero(`
                  let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
                  task.responseText = ${JSON.stringify(requestPatch.text)};
                  task.type = "done";
                  task.responseType = "markdown";
              `);
            });
          }

          // 开始处理流
          window.setTimeout(() => {
            processStream();
          })
        }
        return response;
      });
  };


  // 在Zotero中执行代码
  async function execInZotero(code) {
    try {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "POST",
          url: "http://127.0.0.1:23119/zoterogpt",
          headers: {
            "Content-Type": "application/json",
          },
          responseType: "json",
          data: JSON.stringify({ code }),
          onload: function (response) {
            if (response.status >= 200 && response.status < 300) {
              resolve(response.response.result);
            } else {
              reject(new Error(`Request failed with status: ${response.status}`));
            }
          },
          onerror: function (error) {
            reject(new Error('Network error'));
          }
        });
      });
    } catch (e) {
      window.alert("execInZotero error: " + code);
      return ""
    }
  }

  // 设定ChatGPT输入框文本并发送
  const setText = async (text) => {
    const dispatchInput = (selector) => {
      // 获取 input 输入框的dom对象
      var inputNode = document.querySelector(selector);
      if (!inputNode) { return }
      // 修改input的值
      inputNode.value = text;
      // plus
      inputNode.innerText = text;
      // 设置输入框的 input 事件
      var event = new InputEvent('input', {
        'bubbles': true,
        'cancelable': true,
      });
      inputNode.dispatchEvent(event);
    }
    const originalText = text
    if (AI == "ChatGPT") {
      dispatchInput('#prompt-textarea')
      await sleep(100)
      await send("article", () => {
        const button = document.querySelector('[data-testid="send-button"]');
        button.click()
      })
    } else if (AI == "Gemini") {
      // 获取 input 输入框的dom对象
      const element_input = window.document.querySelector('rich-textarea .textarea');
      // 修改input的值
      element_input.textContent = text;
      await send(".conversation-container", () => {
        const button = document.querySelector('.send-button');
        button.click()
      })
    } else if (AI == "Poe") {
      dispatchInput('textarea[class*=GrowingTextArea_textArea]')
      document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click();
      setTimeout(() => {
        document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click()
      }, 100)
    } else if (AI == "Kimi") {
      const node = document.querySelector("[class^=inputInner]")
      await node[Object.keys(node)[1]].children[1][1].ref.current.insertText(text)

      await send("[class^=segmentItem]", () => {
        const button = document.querySelector('[data-testid=msh-chatinput-send-button]');
        button.click()
      })

    } else if (AI == "Coze") {
      const node = document.querySelector(".b5gKALp6yXERRDn8TV4r")
      node[Object.keys(node)[0]].pendingProps.children[0].props.onSendMessage({ text, mentionList: [] })
    } else if (AI == "Chatglm") {
      const token = document.cookie.match(/chatglm_token=([^;]+);/)[1];
      requestStream({
        api: `https://chatglm.cn/chatglm/backend-api/assistant/stream`,
        token,
        data: {
          "assistant_id": "65940acff94777010aa6b796",
          "conversation_id": localStorage.conversation_id || "",
          "meta_data": {
            "mention_conversation_id": "",
            "is_test": false,
            "input_question_type": "xxxx",
            "channel": "",
            "draft_id": ""
          }, "messages": [
            { "role": "user", "content": [{ "type": "text", "text": text }] }]
        },
        lineRegex: /event:message\ndata: .+/g,
        getContent: (data) => data.parts[0].content[0].type == "text" ? data.parts[0].content[0].text : "",
        midFunction: (data) => {
          if (!localStorage.conversation_id || localStorage.conversation_id.length == 0) {
            localStorage.conversation_id = data.conversation_id
          }
        },
        isNotDelta: true
      })
    } else if (AI == "Yiyan") {
      const node = document.querySelector("#eb_model_footer")
      node[Object.keys(node)[1]].children[3].props.children[2].props.children[0].props.setText(text);
      document.querySelector(".VAtmtpqL").click()
    } else if (AI == "Tongyi") {
      const node = document.querySelector(".chatInput--eJzBH8LP")
      await node[Object.keys(node)[1]].children[0].props.setText(text);

      await send("[class^=questionItem]", () => {
        const node2 = document.querySelector(".operateBtn--zFx6rSR0");
        node2[Object.keys(node2)[1]].onClick()
      })
    } else if (AI == "Claude") {
      const node = document.querySelector("fieldset")
      const props = node[Object.keys(node)[1]].children[0].props.children[0].props.children[0].props;
      props.setInput(text);
      document.querySelector("button[aria-label='Send Message']").click();
    } else if (AI == "MyTan") {
      const conversation_id = location.href.split("chat/")?.[1]
      const data = {
        "content": [
          { "type": "text", "text": text }
        ],
        "stream": true,
      }
      if (conversation_id) {
        data.conversation_id = conversation_id
      } else {
        data.conversation = { title: "新对话", model: JSON.parse(localStorage["chosen-model-obj"]).model }
      }
      requestStream({
        api: `https://mytan.maiseed.com.cn/api/v2/messages`,
        token: JSON.parse(localStorage["chat-tan-token"]).token,
        data,
        lineRegex: /data: .+/g,
        getContent: (data) => data.choices[0].delta.content,
      })
    } else if (AI == "ChanlderAi") {
      // 更新id
      if (!localStorage.conversation_id || localStorage.conversation_id == "") {
        async function readStream(stream) {
          const reader = stream.getReader();
          let result = '';
          const decoder = new TextDecoder();
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            result += decoder.decode(value, { stream: true });
          }
          return result
        }
        const res = await fetch("https://api.chandler.bet/api/chat/chatHistory", {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            "Authorization": `Bearer ${localStorage.token}`,
            "accept": "application/json, text/plain, */*"
          },
          body: JSON.stringify({ "keywords": "", "model_names": [], "page_size": 10, "page_num": 1 }),
        })
        const data = JSON.parse(await readStream(res.body)).data[0]
        localStorage.conversation_id = data.conversation_id
        localStorage.model_name = data.model_name
        localStorage.parent_message_id = data.parent_message_id
      }
      const appData = JSON.parse(localStorage.appLocalStorage)
      requestStream({
        api: `https://api.chandler.bet/api/chat/Chat`,
        token: localStorage.token,
        data: {
          "uid": appData.email,
          "prompt": text,
          "model_name": localStorage.model_name,
          "request_timeout": 30,
          "global_timeout": 100,
          "max_retries": 1,
          "attachment_list": [],
          "parent_message_id": localStorage.parent_message_id,
          "conversation_id": localStorage.conversation_id,
          "answer_again": false,
          "aireply": "",
          "timestamp": (new Date()).getTime(),
          "status": "",
          "app_name": "",
          "web_url": "https://api.chandler.bet"
        },
        lineRegex: /event:message\ndata:.+/g,
        getContent: (data) => data.delta
      })
    } else if (AI == "DeepSeek") {
      requestStream({
        api: `https://chat.deepseek.com/api/v0/chat/completions`,
        token: JSON.parse(localStorage.userToken).value,
        data: {
          "message": text,
          "stream": true,
          "model_preference": null,
          "model_class":
            "deepseek_chat",
          "temperature": 0
        },
        lineRegex: /data: .+/g,
        getContent: (data) => data.choices[0].delta.content || ""
      })
    } else if (AI == "Doubao") {
      const node = document.querySelector("[class^=footer]")
      await node[Object.keys(node)[1]].children.ref.current.autoTransValue(text);
      await sleep(1e3)
      document.querySelector("button#flow-end-msg-send").click();
    } else if (AI == "AIStudio") {
      dispatchInput(".input-wrapper textarea")
      await sleep(100)
      await send("ms-chat-turn", () => {
        const button = document.querySelector('run-button button');
        button.click()
      })
    }
  }

  // 连续发送
  const send = async (selector, callback) => {
    const oldNumber = document.querySelectorAll(selector).length;
    callback();
    await sleep(100);
    while (document.querySelectorAll(selector).length == oldNumber) {
      callback();
      await sleep(100);
    }
  }

  const uploadFile = async (base64String, fileName) => {
    try {
      let type
      if (fileName.endsWith("pdf")) {
        type = "application/pdf"
      } else if (fileName.endsWith("png")) {
        type = "image/png"
      }
      function base64ToArrayBuffer(base64) {
        const binaryString = atob(base64);
        const len = binaryString.length;
        const bytes = new Uint8Array(len);
        for (let i = 0; i < len; i++) {
          bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes.buffer;
      }
      if (AI == "AIStudio") {
        const button = document.querySelector("ms-add-chunk-menu button")
        button && button.click()
      }
      // 创建一个虚拟的 PDF 文件对象
      const fileContent = base64ToArrayBuffer(base64String);
      const file = new File([fileContent], fileName, { type });

      // 创建一个DataTransfer对象
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);
      const fileInput = document.querySelector("input[type=file]") ||
        document.querySelector('[data-testid="file-upload"]') // Claude

      fileInput.files = dataTransfer.files
      fileInput.dispatchEvent(new Event('change', { bubbles: true }));
      // 需要特殊处理的
      if (AI == "AIStudio") {
        const button = document.querySelector("ms-add-chunk-menu button")
        button && button.click()
      }
    } catch (e) {
      // window.alert(e)
    }
  }

  /**
   * {api, token, data, lineRegex, getContent, errorFunction, midFunction}
   * @param {*} data
   */
  const requestStream = async (params) => {
    fetch(params.api, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${params.token}`
      },
      body: JSON.stringify(params.data)
    })
      .then(response => {
        if (response.status == 200) {
          return response.body.getReader()
        } else if (response.status == 400) {
          throw new Error('频率过高');
        } {
          throw new Error('授权失败');
        }
      })
      .then(reader => {
        let text = ""
        const decoder = new TextDecoder();
        window.setTimeout(async () => {
          while (true) {
            const { done, value } = await reader.read();
            if (done) {
              await execInZotero(`
                  let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
                  task.responseText = ${JSON.stringify(text)};
                  task.type = "done";
                  task.responseType = "markdown"
                `)
              break
            }
            try {
              const newLines = decoder.decode(value, { stream: true })
              for (let line of newLines.match(params.lineRegex)) {
                try {
                  const data = JSON.parse(line.split("data:")[1].trim())
                  params.midFunction && params.midFunction(data)
                  text = params.isNotDelta ? params.getContent(data) : (text + params.getContent(data));
                } catch (e) {
                  if (String(e).includes("Stop")) { return }
                }
                execInZotero(`
                    let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
                    task.responseText = ${JSON.stringify(text)};
                    task.type = "pending";
                    task.responseType = "markdown"
                  `)
              }
            } catch (e) {
              console.log(e)
            }
          }
        }, 0)
      })
      .catch(e => {
        params.errorFunction && params.errorFunction()
      })
  }
  // 阻塞
  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  GM_registerMenuCommand('🔗 运行', () => {
    isRunning = true
    window.alert("🔗 已运行")
  });
  GM_registerMenuCommand('🎊 断开', () => {
    isRunning = false
    window.alert("🎊 断开")
  });

  const waitSend = async (selector) => {
    let getUserQuestionNum = () => document.querySelectorAll(selector).length
    const questionNum = getUserQuestionNum()
    while (getUserQuestionNum() == questionNum) {
      await sleep(100)
    }
  }

  // 通信
  await sleep(3000)
  while (true) {
    if (!isRunning) {
      await execInZotero(`
        window.Meet.Connector.time = 0;
      `)
      await sleep(1000)
      continue;
    }
    try {
      const tasks = (await execInZotero(`
        if (!window.Meet.Connector){
          window.Meet.Connector = ${JSON.stringify({
        AI, time: Date.now() / 1e3, tasks: []
      })};
        } else {
          window.Meet.Connector.time = ${Date.now() / 1e3};
          window.Meet.Connector.AI = "${AI}";
        }
        window.Meet.Connector
      `)).tasks
      if (!tasks || tasks.length == 0) {
        await sleep(500)
        continue
      }
      const task = tasks.slice(-1)[0]
      if (task.type == "pending") {
        if (task.file) {
          // document.querySelector("[data-testid='create-new-chat-button']").click();
          // await sleep(1e3)
          await uploadFile(task.file.base64String, task.file.name)
          await execInZotero(`
            let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
            task.type = "done"
          `)
        } else if (task.requestText) {
          await setText(task.requestText)
          // 操作浏览器提问
          await execInZotero(`
            let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
            task.requestText = "";
            task.responseText = "<p>Waiting ${AI}...</p>";
          `)
        } else {
          let isDone = false, text = "", type = "html"
          const setZoteroText = async () => {
            if (typeof (text) !== "string") { return }
            await execInZotero(`
              let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
              task.responseText = ${JSON.stringify(text)};
              task.type = ${isDone} ? "done" : "pending";
              task.responseType = "${type}"
            `)
            if (isDone) {
              await sleep(1000)
              await execInZotero(`
                let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
                task.responseText = ${JSON.stringify(text)};
            `)
            }
          }
          if (AI == "Gemini") {
            const outputEle = [...document.querySelectorAll('.conversation-container')].slice(-1)[0];
            const contentEle = outputEle.querySelector("model-response .response-content message-content")
            if (contentEle) {
              isDone = Boolean(outputEle.querySelector(".complete"))
              text = contentEle.querySelector(".markdown").innerHTML
              await setZoteroText()
            }
          } else if (AI == "Poe") {
            type = "markdown"
            const lastNode = [...document.querySelectorAll("[class^=ChatMessagesView_messagePair]")].slice(-1)[0]
            const props = lastNode[Object.keys(lastNode)[0]].child.memoizedProps
            text = props.pairedMessage.text
            isDone = props.pairedMessage.state == "complete"
            await setZoteroText()
          } else if (AI == "Coze") {
            const outputEle = document.querySelector(".message-group-wrapper");
            const contentEle = outputEle.querySelector("[data-testid='bot.ide.chat_area.message_box'] .flow-markdown-body")
            isDone = Boolean(outputEle.querySelector(".chat-uikit-message-box-container__message__message-box__footer").childNodes.length != 0)
            text = contentEle.innerHTML.replace(/<br .+?>/g, "").replace(/<hr .+?>/g, "<hr/>")
            await setZoteroText()
          } else if (AI == "Yiyan") {
            const outputEle = document.querySelector(".ErXhAgf5 .RmHagX8t");
            const contentEle = outputEle.querySelector(".custom-html.md-stream-desktop")
            isDone = Boolean(outputEle.querySelector(".fXxD0Rtx"))
            text = contentEle.innerHTML.replace(/<br .+?>/g, "").replace(/<hr .+?>/g, "<hr/>")
            await setZoteroText()
          } else if (AI == "Tongyi") {
            const lastAnwser = [...document.querySelectorAll("[class^=answerItem]")].slice(-1)[0]
            type = "markdown"
            const message = lastAnwser[Object.keys(lastAnwser)[0]].memoizedProps.children.find(i => { try { return i.props.children[2].props.message } catch { } }).props.children[2].props.message
            isDone = message.contents[message.contents.length - 1].status == "finished"
            text = message.contents[message.contents.length - 1].content
            await setZoteroText()
          } else if (AI == "Doubao") {
            const nodes = [...document.querySelectorAll("[class^=message-box-content-wrapper]")]
            const node = nodes.slice(-1)[0]
            const data = node[Object.keys(node)[0]].child.child.child.child.memoizedState.memoizedState.current.value
            type = "markdown"
            text = data.content_obj.text;
            isDone = data.ext.is_finish == "1";
            await setZoteroText()
          }
        }
      }
    } catch (e) {
      console.log(e)
      await sleep(1000)
    }
    await sleep(10)
  }
})();

QingJ © 2025

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