Zotero GPT Connector

Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze

// ==UserScript==
// @name         Zotero GPT Connector
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze
// @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/*
// @icon         https://cdn.oaistatic.com/_next/static/media/favicon-32x32.be48395e.png
// @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 = ""
  if (location.host == 'chatgpt.com') {
    AI = "ChatGPT"
  } else if (location.host == 'gemini.google.com') {
    AI = "Gemini"
  } else if (location.host == 'poe.com') {
    AI = "Poe"
  } else if (location.host == 'kimi.moonshot.cn') {
    AI = "Kimi"
  } else if (location.host == 'www.coze.com') {
    AI = "Coze"
  }
  // 在Zotero中执行代码
  async function execInZotero(code) {
    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'));
        }
      });
    });
  }

  // 设定ChatGPT输入框文本并发送
  const setText = (text) => {
    const originalText = text
    if (AI == "ChatGPT") {
      // 获取 input 输入框的dom对象
      var element_input = window.document.querySelector('#prompt-textarea');
      // 修改input的值
      element_input.value = text;
      // 设置输入框的 input 事件
      var event = new InputEvent('input', {
        'bubbles': true,
        'cancelable': true,
      });
      element_input.dispatchEvent(event);
      const buttons = document.querySelector("#prompt-textarea").parentElement.parentElement.querySelectorAll("button");
      const button = buttons[buttons.length-1]
      button.click()
      setTimeout(() => {
        button.click()
      }, 100)
    } else if (AI == "Gemini") {
      // 获取 input 输入框的dom对象
      var element_input = window.document.querySelector('rich-textarea .textarea');
      // 修改input的值
      element_input.textContent = text;
      document.querySelector(".send-button").click();
      setTimeout(() => {
        document.querySelector(".send-button").click()
      }, 100)
    } else if (AI == "Poe") {
      var element_input = window.document.querySelector('textarea[class*=GrowingTextArea_textArea]');
      element_input.value = text;
      // 设置输入框的 input 事件
      var event = new InputEvent('input', {
        'bubbles': true,
        'cancelable': true,
      });
      element_input.dispatchEvent(event);
      document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click();
      setTimeout(() => {
        document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click()
      }, 100)
    } else if (AI == "Kimi") {
      fetch(`https://kimi.moonshot.cn/api/chat/${location.href.match(/chat\/([0-9a-z]+)/)[1]}/completion/stream`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          "Authorization": `Bearer ${localStorage.access_token}`
        },
        body: JSON.stringify({ "messages": [{ "role": "user", "content": text }], "refs": [], "use_search": false })
      })
        .then(response => {
          if (response.status == 200) {
            return response.body.getReader()
          } else {
            throw 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.tasks[window.Meet.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(/data: .+/g)) {
                  try {
                    const data = JSON.parse(line.split("data: ")[1])
                    if (data.event && data.event == "cmpl") {
                      text += data.text
                    } else if (data.error) {
                      text += data.error.message;
                      console.log(data);
                      document.querySelector("[data-testid=msh-sidebar-new]").click();
                      window.setTimeout(() => {
                          setText(originalText)
                      }, 1000)
                      return
                    }
                  } catch { }
                  execInZotero(`
                    let task = window.Meet.tasks[window.Meet.tasks.length-1]
                    task.responseText = ${JSON.stringify(text)};
                    task.type = "pending";
                    task.responseType = "markdown"
                  `)
                }
              } catch (e) {
                console.log(e)
              }

            }
          }, 0)
        })
        .catch(e => {
          console.log(e)

          window.setTimeout(async () => {
            const res = await fetch("https://kimi.moonshot.cn/api/auth/token/refresh", {
              headers: {
                "Authorization": `Bearer ${localStorage.refresh_token}`
              }
            })
            const data = await res.json()
            localStorage.access_token = data.access_token
            localStorage.refresh_token = data.refresh_token
            setText(text)
          })
        })
    } else if (AI == "Coze") {
      const node = document.querySelector(".b5gKALp6yXERRDn8TV4r")
      node[Object.keys(node)[0]].pendingProps.children[0].props.onSendMessage({text, mentionList: []})
    }
  }

  // 阻塞
  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

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


  // 通信
  await sleep(3000)
  while (true) {
    if (!isRunning) {
      await execInZotero(`
        window.Meet.tasks = undefined
      `)
      await sleep(1000)
      continue;
    }
    try {
      const result = await execInZotero(`
        if (!window.Meet.tasks){
          window.Meet.tasks = []
        } else {
          window.Meet.tasks
        }
      `)
      const tasks = result;
      if (!tasks || tasks.length == 0) {
        await sleep(500)
        continue
      }
      const task = tasks.slice(-1)[0]
      if (task.type == "pending") {
        if (task.requestText) {
          // 操作浏览器提问
          if (AI == "ChatGPT") {
            let getUserQuestionNum = () => document.querySelectorAll("[data-message-author-role=user]").length
            const questionNum = getUserQuestionNum()
            setText(task.requestText)
            while (getUserQuestionNum() == questionNum) {
              await sleep(100)
            }
          } else if (AI == "Gemini") {
            let getUserQuestionNum = () => document.querySelectorAll("user-query").length
            const questionNum = getUserQuestionNum()
            setText(task.requestText)
            while (getUserQuestionNum() == questionNum) {
              await sleep(100)
            }
            while (document.querySelectorAll('model-response').length != getUserQuestionNum()) {
              await sleep(100)

            }
          } else if (AI == "Poe") {
            let getUserQuestionNum = () => document.querySelectorAll("[class*=ChatMessage_humanMessageWrapper]").length
            const questionNum = getUserQuestionNum()
            setText(task.requestText)
            while (getUserQuestionNum() == questionNum) {
              await sleep(100)
            }
            while (document.querySelectorAll('[class*=Message_botMessageBubble]').length != getUserQuestionNum()) {
              await sleep(100)
            }
          } else if (AI == "Kimi") {
            setText(task.requestText)
          } else if (AI == "Coze") {
            setText(task.requestText)
          }
          await execInZotero(`
            let task = window.Meet.tasks[window.Meet.tasks.length-1]
            task.requestText = "";
            task.responseText = "<p>Answering (本过程不消耗Api Key额度)...</p>";
          `)
        } else {
          let isDone = false, text = "", type = "html"
          const setZoteroText = async () => {
            await execInZotero(`
              let task = window.Meet.tasks[window.Meet.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.tasks[window.Meet.tasks.length-1]
                task.responseText = ${JSON.stringify(text)};
            `)
            }
          }
          if (AI == "ChatGPT") {
            const outputEle = [...document.querySelectorAll('[data-testid^=conversation-turn]')].slice(-1)[0];
            // const contentEle = outputEle.querySelector("div>div>div:nth-child(2)>div:nth-child(2)>div")
            isDone = Boolean(outputEle.querySelector("span[data-state=closed]"))
            text = outputEle.querySelector(".markdown").innerHTML
            await setZoteroText()
          } else if (AI == "Gemini") {
            const outputEle = [...document.querySelectorAll('model-response')].slice(-1)[0];
            const contentEle = outputEle.querySelector(".response-content message-content")
            isDone = Boolean(outputEle.querySelector(".complete"))
            text = contentEle.querySelector(".markdown").innerHTML
            await setZoteroText()
          } else if (AI == "Poe") {
            const outputEle = [...document.querySelectorAll('[class*=Message_botMessageBubble]')].slice(-1)[0];
            const contentEle = outputEle.querySelector("[class*=Markdown_markdownContainer]")
            isDone = Boolean(document.querySelector("[class*=ChatMessageActionBar_actionBar]"))
            text = contentEle.innerHTML
            await setZoteroText()
          } else if (AI == "Kimi") {
            // 无需这一步
          } 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()
          }
        }
      }
    } catch (e) {
      console.log(e)
    }
    await sleep(100)
  }
})();