Zotero GPT Connector
Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm Yiyan Tongyi Claude Mytan ChanlderAi DeepSeek Doubao AIStudio MicrosoftCopilot Wenxiaobai Grok
Today's Installations
291
Total Installations
25,733
Creation Date
a month ago
Update Date
3 days ago
User Rating
No Rating
v4.0.9

// ==UserScript==
// @name Zotero GPT Connector
// @description Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm Yiyan Tongyi Claude Mytan ChanlderAi DeepSeek Doubao AIStudio MicrosoftCopilot Wenxiaobai Grok
// @namespace http://tampermonkey.net/
// @icon https://github.com/MuiseDestiny/zotero-gpt/blob/bootstrap/addon/chrome/content/icons/favicon.png?raw=true
// @version 4.0.2
// @author Polygon
// @noframes
// @match https://chatgpt.com/*
// @match https://gemini.google.com/*
// @match https://poe.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.*/*
// @match https://node.dawuai.buzz/*
// @match https://aistudio.google.com/*
// @match https://claude.ai0.cn/*
// @match https://grok.com/*
// @include /.+rawchat.+/
// @include /.+chatgpt.+/
// @include /.+claude.+/
// @include /.+qwen.+/
// @include /.+coze.+/
// @match https://www.zaiwen.top/chat/*
// @match https://chat.aite.lol/*
// @match https://yuanbao.tencent.com/chat/*
// @match https://chatgptup.com/*
// @match https://kelaode.kelaodeshare.com/*
// @match https://ihe5u7.aitianhu2.top/*
// @match https://cc01.plusai.io/*
// @match https://arc.aizex.me/*
// @match https://.chatopens.net/
// @match https://www.chatwb.com/*
// @match https://www.xixichat.top/*
// @match https://zchat.tech/*
// @match https://.sorryios.chat/
// @match https://monica.im/*
// @match https://copilot.microsoft.com/*
// @match https://gptsdd.com/*
// @match https://max.bpjgpt.top/*
// @match https://nbai.tech/
// @match https://x.liaobots.work/*
// @match https://x.liaox.ai/*
// @match https://chat.qwenlm.ai/*
// @match https://lke.cloud.tencent.com/*
// @match https://dazi.co/*
// @match https://www.wenxiaobai.com/*
// @match https://www.techopens.com/*
// @match https://xiaoyi.huawei.com/*
// @match https://chat.baidu.com/*
// @match https://qrms.com/*
// @match https://www.perplexity.ai/*
// @connect *
// @connect https://kimi.moonshot.cn/*
// @connect https://chatglm.cn/*
// @connect https://chat.deepseek.com/*
// @connect https://chatgpt.com/*
// @connect http://127.0.0.1:23119/zoterogpt
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @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.includes('coze')) {
AI = "Coze"
} else if (host == "chatglm.cn") {
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.includes("claude") || host.includes("kelaodeshare")) {
AI = "Claude"
} else if (host == 'mytan.maiseed.com.cn') {
AI = "MyTan"
} else if (host == 'mychandler.bet') {
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"
} else if (host == "www.zaiwen.top") {
AI = "Zaiwen"
} else if (host == 'yuanbao.tencent.com') {
AI = "Yuanbao"
} else if (host == "www.tiangong.cn") {
AI == "Tiangong"
} else if (host == 'monica.im') {
AI = "Monica"
} else if (host == 'copilot.microsoft.com') {
AI = "Copilot"
} else if (location.host.includes('qwen')) {
AI = "Qwen"
} else if (location.host == 'lke.cloud.tencent.com') {
AI = "TencentDeepSeek"
} else if (location.host == 'dazi.co') {
AI = "AskManyAI"
} else if (location.host == 'www.wenxiaobai.com') {
AI = "Wenxiaobai"
} else if (location.host == 'grok.com') {
AI = "Grok"
} else if (location.host == 'xiaoyi.huawei.com') {
AI = "Xiaoyi"
} else if (location.host == 'chat.baidu.com') {
AI = "Baidu"
} else if (location.host == 'www.perplexity.ai') {
AI = "Perplexity"
}
const requestPatchArr = [
{
AI: "Kimi",
regex: /https://kimi.moonshot.cn/api/chat/.+/completion/stream/,
extract: function (text, allText) {
let resp = "", think = ""
for (let line of allText.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.event == "cmpl") {
resp += data.text || ""
} else if (data.event.match(/^k.+/)) {
think += data.text || ""
}
}
}
if (resp == "") {
if (think != "") {
this.text = ">" + think.replace(/\n/g, "n>")
}
} else {
this.text = resp
}
},
text: ""
},
{
AI: "AIStudio",
regex: /GenerateContent$/,
extract: function (text) {
let data
while (!data) {
try {
data = JSON.parse(text)
}catch {
text += "]"
}
}
console.log(data)
// this.text = data[0].map(i => (i[0][0][0][0][0][12] ? ">" : "") + i[0][0][0][0][0][1]).join("")
let think = "", resp = ""
for (let i of data[0]) {
let s = i[0][0][0][0][0][1]
if (i[0][0][0][0][0][12]) {
think += s
} else {
resp += s
}
}
if (resp == "") {
if (think) {
this.text = ">" + think.replace(/\n/g, "\n>")
}
} else {
this.text = resp
}

  },
  text: "",
},
{
  AI: "ChatGPT",
  regex: /backend-api\/conversation$/,
  extract: function (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])
        const streamPath = "/message/content/parts/0"
        if (Object.keys(data).length == 1 && typeof (data.v) == "string" && this.p == streamPath) {
          this.text += data.v
        } else if ((this.p == streamPath || data.p == streamPath)) {
          this.p = streamPath
          if (data.o && data.o == "add") {
            this.text = ""
          }
          if (typeof (data.v) == "string") {
            this.text += data.v
          } else if (Array.isArray(data.v)) {
            const d = data.v.find(i => i.p == streamPath)
            if (d && typeof (d.v) == "string") {
              this.text += d.v
            }
          }
        } else {
          this.p = ""
        }
      } else if (line.startsWith("data: [")) {
        try {
          const delta = JSON.parse(line.replace(/^data:\s/, "")).slice(-1)[0]
          if (typeof (delta) == "string" ) {
            this.text += delta
          }
        } catch {}
      }
    }
  },
  p: "",
  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 || ""
        } else if (data.type && data.type == "content_block_delta") {
          this.text += data.delta.text || ""
        }
      }
    }
  },
  text: ""
},
{
  AI: "Chatglm",
  regex: /stream/,
  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])
        try {
          if (data.parts && data.parts[0] && data.parts[0].content[0].type == "text") {
            this.text = data.parts[0].content[0].text
          }
        } catch (e) { console.log("extract", e) }
      }
    }
  },
  text: ""
},
{
  AI: "Zaiwen",
  regex: /admin\/chatbot$/,
  extract: function (text) {
    this.text = text

  },
  text: ""
},
{
  AI: "Yuanbao",
  regex: /api\/chat\/.+/,
  extract: function (allText) {
    let think = "", resp = ""
    for (let line of allText.split("\n")) {
      if (line.startsWith("data: {")) {
        try { JSON.parse(line.split("data: ")[1]) } catch { continue }
        const data = JSON.parse(line.split("data: ")[1])
        try {
          if (data.type == "text") {
            resp += (data.msg || "")
          } else if (data.type == "think") {
            think += (data.content || "")
          }
        } catch (e) { console.log("extract", e) }
      }
    }
    if (resp == "") {
      if (think != "") {
        this.text = ">" + think.replace(/\n/g, "n>")
      }
    } else {
      this.text = resp
    }
  },
  text: ""
},
{
  AI: "DeepSeek",
  regex: /completion$/,
  extract: function (text) {
    let resp = "", think = ""
    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])
        try {
          if (data.choices[0].delta.type == "thinking") {
            think += (data.choices[0].delta.content || "")
          } else if (data.choices[0].delta.type == "text") {
            resp += (data.choices[0].delta.content || "")
          }
        } catch (e) { console.log("extract", e) }
      }
    }
    if (resp == "") {
      if (think) {
        this.text = ">" + think.replace(/\n/g, "\n>")
      }
    } else {
      this.text = resp
    }
  },
  text: "",
},
{
  AI: "ChanlderAi",
  regex: /api\/chat\/Chat$/,
  extract: function (text) {
    for (let line of text.split("\n")) {
      console.log("line", line)
      if (line.startsWith("data:{")) {
        try { JSON.parse(line.split("data:")[1]) } catch { continue }
        const data = JSON.parse(line.split("data:")[1])
        try {
          this.text += data.delta
        } catch (e) { console.log("extract", e) }
      }
    }

  },
  text: ""
},
{
  AI: "Yiyan",
  regex: /chat\/conversation\/v2$/,
  extract: function (text, allText) {
    let delta = ""
    for (let line of allText.split("\n\nevent:message\n")) {
      if (line.startsWith("data:{")) {
        try { JSON.parse(line.split("data:")[1]) } catch { continue }
        const data = JSON.parse(line.split("data:")[1])
        try {
          delta += data.data.text || ""
        } catch (e) { console.log("extract", e) }
      }
    }
    this.text = delta
  },
  text: "",
},
{
  AI: "Doubao",
  regex: /samantha\/chat\/completion/,
  extract: function (text, allText) {
    for (let line of text.split("\n")) {
      if (line.startsWith("data:")) {
        try {
          const data = JSON.parse(JSON.parse(line.slice(5)).event_data)
          if (JSON.parse(data.message.content).type != 1) {
            this.text += JSON.parse(data.message.content).text || ""
          }
        } catch {}
      }
    }
  },
  text: "",
},
{
  AI: "Monica",
  regex: /api.monica.im\/api\/custom_bot\/chat/,
  extract: function (text, allText) {
    let think = "", resp = ""
    for (let line of allText.split("\n")) {
      if (line.startsWith("data:")) {
        const data = JSON.parse(line.slice(5))
        if (Boolean(data.agent_status) && Boolean(data.agent_status.type == "thinking_detail_stream")) {
          think += (data.agent_status.metadata.reasoning_detail || "")
        }
        resp += data.text
      }
    }
    think = ">" + think.replace(/\n/g, "\n>")
    if (resp.length == 0) {
      this.text = think
    } else {
      this.text = resp
    }
  },
  text: "",
},
{
  AI: "Qwen",
  regex: /chat\/completions$/,
  extract: function (text, allText) {
    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])
        try {
          this.text += data.choices[0].delta.content
        } catch (e) { console.log("extract", e) }
      }
    }
  },
  text: "",
},
{
  AI: "AskManyAI",
  regex: /engine\/sseQuery$/,
  extract: function (text, allText) {
    let think = "", resp = ""
    for (let line of allText.split("\n")) {
      if (line.startsWith("data: {")) {
        try { JSON.parse(line.split("data:")[1]) } catch { continue }
        const data = JSON.parse(line.split("data:")[1])
        try {
          if (data.content.startsWith("[HIT-REF]")) { continue}
          if (data.event == "thinking") {
            think += data.content
          } else if (data.event == "resp") {
            resp += data.content
          }
        } catch (e) { console.log("extract", e) }
      }
    }
    if (resp.length >0) {
      this.text = resp
    } else {
      this.text = think
    }
  },
  text: "",
},
{
  AI: "Wenxiaobai",
  regex: /conversation\/chat\/v1$/,
  extract: function (_, allText) {
    if (!allText) { return }
    let resp = ""
    for (let line of allText.replace(/event:message\ndata/g, "message").split("\n")) {
      if (line.startsWith("message:{")) {
        try { JSON.parse(line.split("message:")[1]) } catch { continue }
        const data = JSON.parse(line.split("message:")[1])
        try {
            resp += data.content || ""
        } catch (e) { console.log("extract", e) }
      }
    }
    console.log(resp)
    resp = resp.replace(/^```ys_think[\s\S]+?\n\n```\n/, "").replace(/[\s\S]+?```ys_think/, "```ys_think")
    if (resp.includes("```ys_think")) {
      resp = ">"+resp.split("\n").slice(3).join("\n>")
    }
    this.text = resp
  },
  text: "",
},
{
  AI: "Coze",
  regex: /conversation\/chat/,
  extract: function (text, allText) {
    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])
        try {
          if (data.message.type == "answer") {

            this.text += data.message.content || ""
          }
        } catch (e) { console.log("extract", e) }
      }
    }
  },
  text: "",
},
{
  AI: "Grok",
  regex: /responses$/,
  extract: function (text, allText) {
    let resp="", think=""
    for (let t of allText.split("\n") ) {
      let data
      try {
        data = JSON.parse(t).result
      } catch {continue}
      if (data.isThinking) {
        think += data.token || ""
      } else{
        resp += data.token || ""
      }
    }
    if (resp == "") {
      if (think) {
        this.text = ">" + think.replace(/\n/g, "\n>")
      }
    } else {
      this.text = resp
    }
  },
  text: "",
},
{
  AI: "Baidu",
  regex: /conversation$/,
  extract: function (text, allText) {
    let resp = "", think = ""
    for (let t of allText.split("\n")) {
      if (!t.startsWith("data:")) {continue}
      let data
      console.log(t.slice(5))
      try {
        data = JSON.parse(t.slice(5)).data
      } catch { continue }
      console.log(data)
      if (!data) { continue}
      if (data.message.metaData.state == "generating-resp") {
        if (data.message.content.generator.component == "reasoningContent") {
          think += data.message.content.generator.data.value || ""
        } else if (data.message.content.generator.component == "markdown-yiyan"){
          resp += data.message.content.generator.data.value || ""
        }
      }
    }
    if (resp == "") {
      if (think) {
        this.text = ">" + think.replace(/\n/g, "\n>")
      }
    } else {
      this.text = resp
    }
  },
  text: "",
},
{
  AI: "MyTan",
  regex: /messages$/,
  extract: function (text, allText) {
    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])
        try {
            this.text += data.choices[0].delta.content
          
        } catch (e) { console.log("extract", e) }
      }
    }
  },
  text: "",
},
{
  AI: "Perplexity",
  regex: /perplexity_ask$/,
  extract: function (text, allText) {
    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])
        try {
          for (let block of data.blocks) {
            if (block.intended_usage == "ask_text") {
              this.text = block.markdown_block.answer
            }
          }

        } catch (e) { console.log("extract", e) }
      }
    }
  },
  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.AI == AI && i.regex.test(url)))) {
execInZotero( let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1]; task.responseText = ${JSON.stringify(requestPatch.text || "")}; task.responseType = "markdown"; );
if (this.readyState === 3) {
try {
requestPatch.extract(this.responseText)
} catch(e) {
console.log("error extract", e, 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 ([0, 4].includes(this.readyState)) {
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 => {
console.log("fetch")
const url = response.url
const requestPatch = requestPatchArr.find(i => i.AI == AI && i.regex.test(url))
if (requestPatch) {
requestPatch.text = ""
execInZotero( let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1]; task.responseText = ${JSON.stringify(requestPatch.text)}; task.responseType = "markdown"; );
const clonedResponse = response.clone();
console.log("requestPatch", requestPatch)
console.log(clonedResponse)
const reader = clonedResponse.body.getReader();
const decoder = new TextDecoder()
let allText = ""
function processStream() {
reader.read().then(({ done, value }) => {
if (done) {
console.log(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"; );
requestPatch.text = ""
return;
}

          // 将 Uint8Array 转为字符串
          const text = decoder.decode(value, { stream: true });
          allText += text
          try {
            requestPatch.extract(text, allText)
          } catch (e) { console.log("requestPatch.extract(text)", e) }
          execInZotero(`
              let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
              task.responseText = ${JSON.stringify(requestPatch.text || "")};
              task.responseType = "markdown";
          `);

          // 递归调用,继续读取流数据
          processStream();
        }).catch(error => {
          // 捕获所有错误,包括 AbortError
          console.log("Error when Patch", error)
          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 = ""
        });
      }

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

};

// 在Zotero中执行代码
async function execInZotero(code) {
code = 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}"; } ${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, type = "plain") => {
// 获取 input 输入框的dom对象
var inputNode = document.querySelector(selector);
if (!inputNode) { return }
// 修改input的值

  inputNode.value = text;
  // plus
  try {
    inputNode.innerHTML = text.split("\n").map(i => `<p>${i}</p>`).join("\n");
  } catch { }
  

  // 设置输入框的 input 事件
  var event = new InputEvent('input', {
    'bubbles': true,
    'cancelable': true,
  });
  inputNode.dispatchEvent(event);
}
const setTextareaValue = async (textarea) => {
  const props = Object.values(textarea)[1]

  // 获取目标 DOM 节点(假设 temp2 是 DOM 元素引用)
  const targetElement = textarea;

  // 创建伪事件对象
  const e = {
    target: targetElement,
    currentTarget: targetElement,
    type: 'change',
  };

  // 手动设置值(需同时更新 DOM 和 React 状态)
  targetElement.value = text;

  // 触发 React 的 onChange 处理
  await props.onChange(e);
}

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("[data-button-send=true]").click()
  setTimeout(() => {
    document.querySelector("[data-button-send=true]").click()
  }, 100)
} else if (AI == "Kimi") {
  const editor = document.querySelector(".chat-input-editor").__lexicalEditor
  await editor.setEditorState(
    editor.parseEditorState(
      {
        "root": {
          "children": [
            {
              "children": [
                {
                  "detail": 0,
                  "format": 0,
                  "mode": "normal",
                  "style": "",
                  "text": text,
                  "type": "text",
                  "version": 1
                }
              ],
              "direction": null,
              "format": "",
              "indent": 0,
              "type": "paragraph",
              "version": 1,
              "textFormat": 0
            }
          ],
            "direction": null,
              "format": "",
                "indent": 0,
                  "type": "root",
                    "version": 1
        }
      }
    )
  )
  await send(".chat-content-item", () => {
    const button = document.querySelector('.send-button');
    button.click()
  })

} else if (AI == "Coze") {
  const textarea = document.querySelector("textarea.rc-textarea")
  await setTextareaValue(textarea)
  await sleep(100)
  await send("[data-message-id]", () => {
    const button = document.querySelector('button[data-testid="bot-home-chart-send-button"]');
    button.click()
  })
} else if (AI == "Chatglm") {
  dispatchInput(".input-box-inner textarea")
  await send(".item.conversation-item", () => {
    const button = document.querySelector('.enter img');
    if (button) {
      const mouseDownEvent = new MouseEvent('mousedown', {
        bubbles: true,
        cancelable: true
      });
      button.dispatchEvent(mouseDownEvent);
    }
  })
} else if (AI == "Yiyan") {
  const node = document.querySelector(".oeNDrlEA")
  await node[Object.keys(node)[1]].children[2].props.children[0].props.onChange(text)
  await sleep(1e3)
  await send(".dialogue_card_item", () => {
    document.querySelector("#sendBtn").click()
  },1e3)
} else if (AI == "Tongyi") {
  setTextareaValue(document.querySelector("textarea.ant-input"))
  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;
  await props.setPrompt(text);
  await sleep(100)
  document.querySelector("button[aria-label='Send Message']").click();
} else if (AI == "MyTan") {
  dispatchInput(".talk-textarea")
  await sleep(100)
  await send(".message-container .mytan-model-avatar", () => {
    const button = document.querySelector('.send-icon');
    button.click()
  })
} else if (AI == "ChanlderAi") {
  dispatchInput(".chandler-content_input-area")
  await sleep(100)
  await send(".chandler-ext-content_communication-group", () => {
    const button = document.querySelector('.send');
    button.click()
  })
} else if (AI == "DeepSeek") {
  const node = document.querySelector("#chat-input")
  node[Object.keys(node)[1]].onChange({ currentTarget: { value: text } })
  await sleep(100)
  await send(".f9bf7997", () => {
    const button = document.querySelector('.f6d670');
    button.click()
  })
} else if (AI == "Doubao") {
  setTextareaValue(document.querySelector("[class^=chat-input-container] textarea") )
  await sleep(100)
  await send("[class^=message-block-container]", () => {
    const button = document.querySelector("button#flow-end-msg-send");
    button.click()
  })
} else if (AI == "AIStudio") {
  dispatchInput(".text-wrapper textarea")
  await sleep(100)
  await send("ms-chat-turn", () => {
    const button = document.querySelector('run-button button');
    button.click()
  })
} else if (AI == "Zaiwen") {
  dispatchInput('textarea.arco-textarea')
  await sleep(100)
  await send(".sessions .item", () => {
    const button = document.querySelector('img.send');
    button.click()
  });
} else if (AI == "Yuanbao") {
  dispatchInput('.chat-input-editor .ql-editor')
  await sleep(100)
  await send(".agent-chat__bubble__content", () => {
    const button = document.querySelector('.icon-send');
    button.click()
  })
} else if (AI == "Monica") {
  const elements = document.querySelectorAll(".chat--PCM74");

  const visibleElements = [];

  // 遍历所有元素
  elements.forEach(element => {
    if (element.style.display !== 'none') {
      // 如果不是 'none',将其添加到数组
      visibleElements.push(element);
    }
  });

  visibleElements.forEach(element => {
    element.parentNode.insertBefore(element, element.parentNode.firstChild);
  });

  const textarea = document.querySelector('textarea.ant-input')
  textarea[Object.keys(textarea)[1]].onChange({ target: { value: text }, currentTarget: { value: text } })
  await sleep(100)
  await send("[class^=chat-message]", () => {
    const button = document.querySelector('[class^=input-msg-btn]')
    button[Object.keys(button)[1]].onClick({ isTrusted: true, stopPropagation: () => { } })
  })
} else if (AI == "Copilot") {
  const node = document.querySelector("textarea#userInput").parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
  node[Object.keys(node)[1]].children[1].props.onSubmit(text)
} else if (AI == "Qwen") {
  dispatchInput("#chat-input")
  await sleep(100)
  await send("[id^=message]", () => {
    const button = document.querySelector('#send-message-button');
    button.click()
  })
} else if (AI == "TencentDeepSeek") {
  document.querySelector(".question-input").__vue__.onStopStream()
  const div = document.querySelector(".question-input-inner__textarea")
  await div.__vue__.onChange(text.slice(0,10000))
  const chatDiv = document.querySelector(".client-chat");
  const total = chatDiv.__vue__.msgList.length
  // 等待新回答
  while (chatDiv.__vue__.msgList.length - total < 1) {
    document.querySelector(".question-input").__vue__.onSendQuestion()
    await sleep(100)
  }
  // 等待新回答
  while (chatDiv.__vue__.msgList.length - total < 2) {
    await sleep(100)
  }
} else if (AI == "AskManyAI") {
  dispatchInput('textarea.textarea_input')
  await sleep(100)
  await send(".main-box-center .list", () => {
    const button = document.querySelector('.chat-input-button-inner .fs_button');
    button.click()
  })
} else if (AI == "Wenxiaobai") {
  const textarea = document.querySelector('[class^=MsgInput_input_box] textarea')
  await setTextareaValue(textarea)
  await sleep(100)
  await send("[class^=Answser_answer_content]", async () => {
    document.querySelector("[class*=MsgInput_send_btn]").parentNode.click()
  })
} else if (AI == "Grok") {
  const textarea = document.querySelector('textarea')
  await setTextareaValue(textarea)
  await sleep(100)
  await send(".items-center .items-start", async () => {
    document.querySelector("button[type=submit]")?.click()
  })
} else if (AI == "Xiaoyi") {
  dispatchInput('textarea')
  await sleep(100)
  await send(".receive-box", () => {
    const button = document.querySelector('.send-button');
    button.click()
  })
} else if (AI == "Baidu") {
  document.querySelector("#chat-input-box").innerText = text
  await sleep(100)
  await send("[class^=index_answer-container]", () => {
    const button = document.querySelector('.send-icon');
    button.click()
  })
} else if (AI == "Perplexity") {
  setTextareaValue(document.querySelector("[class^=bottom] textarea"))
  await send(".-inset-md", () => {
    const node = document.querySelector("button[aria-label=Submit]");
    node.click()
  })
}

}

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

const uploadFile = async (base64String, fileName) => {
try {
let fileType;
if (fileName.endsWith("pdf")) {
fileType = "application/pdf";
} else if (fileName.endsWith("png")) {
fileType = "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;
}

  const AIData = {
    ChatGPT: {
      uploadMethod: "drag",
      selector: "form",
    },
    Tongyi: {
      uploadMethod: "drag",
      selector: "[class^=chatInput]",
    },
    Kimi: {
      uploadMethod: "input",
      selector: "input[type=file]"
    },
    Claude: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    AIStudio: {
      uploadMethod: "drag",
      selector: ".text-wrapper",
    },
    Chatglm: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Doubao: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Zaiwen: {
      uploadMethod: "drag",
      selector: ".arco-upload-draggable",
    },
    DeepSeek: {
      uploadMethod: "drag",
      selector: ".bf38813a",
    },
    Yuanbao: {
      uploadMethod: "drag",
      selector: ".agent-chat__input-box"
    },
    ChanlderAi: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Yiyan: {
      uploadMethod: "drag",
      selector: ".UxLYHqhv",
    },
    Poe: {
      uploadMethod: "drag",
      selector: ".ChatDragDropTarget_dropTarget__1WrAL"
    },
    Monica: {
      uploadMethod: "drag",
      selector: "[class^=chat-input-v2]"
    },
    Copilot: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Qwen: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    TencentDeepSeek: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    AskManyAI: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Wenxiaobai: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Coze: {
      uploadMethod: "input",
      selector: "input[type=file]",
    },
    Baidu: {
      uploadMethod: "drag",
      selector: "[class^=chat-bottom-wrapper]"
    },
    Gemini: {
      uploadMethod: "drag",
      selector: "input-area-v2"
    }
  };
  if (!AIData[AI]) {
    AIData[AI] = {
      uploadMethod: "input",
      selector: '[data-test-id="chat-history-container"]',
    }
  }
  if (AIData[AI]) {
    const { uploadMethod, selector, until } = AIData[AI];

    if (uploadMethod === "input") {
      const button = document.querySelector(selector);
      button && button.click();

      // 创建一个虚拟的文件对象
      const fileContent = base64ToArrayBuffer(base64String);
      const file = new File([fileContent], fileName, { type: fileType });

      // 创建一个DataTransfer对象,并添加文件
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);

      let fileInput = document.querySelector(selector);
      if (fileInput) {
        fileInput.files = dataTransfer.files;
        fileInput.dispatchEvent(new Event('change', { bubbles: true }));
      } else {
        window.alert(AI + "网页未加载完毕,或改动导致未获取到fileInput,请联系开发者修复")
      }
    } else if (uploadMethod === "drag") {
      // 创建一个虚拟的文件对象
      const fileContent = base64ToArrayBuffer(base64String);
      const file = new File([fileContent], fileName, { type: fileType });

      // 创建一个DataTransfer对象,并添加文件
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);

      // 查找可拖放的区域或上传区域
      const dropZone = document.querySelector(selector); // 使用提供的选择器查找拖放区域
      if (!dropZone) {
        window.alert(AI + "未获取到dropZone,请联系开发者修复")
      }

      // 创建dragenter, dragover, drop事件
      const dragStartEvent = new DragEvent("dragstart", {
        bubbles: true,
        dataTransfer: dataTransfer,
        cancelable: true
      });
      const dropEvent = new DragEvent("drop", {
        bubbles: true,
        dataTransfer: dataTransfer,
        cancelable: true
      });

      // 依次派发事件,模拟拖放过程
      dropZone.dispatchEvent(dragStartEvent);
      dropZone.dispatchEvent(dropEvent);
    }
    if (until) {
      await sleep(100)
      while (!until()) {
        await sleep(100)
      }
    }
  }
} catch (e) {
  console.error(e);
}

};

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

// 支持:多个联动页面打开
const LOCK_KEY = 'gpt_connector_running';
const TAB_ID = Math.random().toString(36).substr(2, 9); // Unique ID for each tab

GM_registerMenuCommand('⭐️ 优先', () => {
isRunning = true
releaseLock()
acquireLock()
window.alert("⭐️ 优先")
});

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

GM_registerMenuCommand('🎊 断开', () => {
isRunning = false
releaseLock()
window.alert("🎊 断开")
});

function acquireLock() {
let lockInfo = JSON.parse(GM_getValue(LOCK_KEY, "{}"));

if (lockInfo && lockInfo.isLocked) {
  if (lockInfo.tabId === TAB_ID) {
    // The current tab already holds the lock
    // console.log('This tab already holds the lock:', TAB_ID);
    return true;
  } else {
    // Lock is held by another tab
    // console.log('Another tab is already running the script. Exiting...');
    return false;
  }
} else {
  // Lock is not set, acquire it for this tab
  GM_setValue(LOCK_KEY, JSON.stringify({ isLocked: true, tabId: TAB_ID }));
  // console.log('Lock acquired by tab:', TAB_ID);
  return true;
}

}

function releaseLock() {
GM_setValue(LOCK_KEY, JSON.stringify({ isLocked: false, tabId: null }));
}

// Add an event listener to release the lock when the page is unloaded
window.addEventListener('beforeunload', releaseLock);
window.addEventListener('reload', releaseLock);

setInterval(async () => {
await execInZotero( async function getMD5(path) { function arrayBufferToBase64(buffer) { let binary = ''; const bytes = new window.Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } const res = await Zotero.HTTP.request("GET", path, { responseType: "arraybuffer" }) return Zotero.Utilities.Internal.md5(arrayBufferToBase64(res.response)) } (async () => { const md5 = await getMD5("chrome://zoterogpt/content/icons/favicon.png") if (md5 != "387f99eeaa7b0b6d9d3e311017098e37") { window.Meet = 0 } })() )
}, 10e3)
releaseLock()
// 通信
while (true) {
if (!acquireLock()) {
await sleep(1000)
continue;
}
if (!isRunning) {
await execInZotero( window.Meet.Connector.time = 0; )
await sleep(1000)
continue;
}
try {
const tasks = (await execInZotero( 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 || task.files && task.responseText == undefined) {
      await execInZotero(`
        let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
        task.type = "done"
      `)
      if (task.file) {
        await uploadFile(task.file.base64String, task.file.name)
      } else if (task.files){
        for (let file of task.files) {
          await uploadFile(file.base64String, file.name)
        }
      }
    } else if (task.requestText) {
      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^=ChatMessage_chatMessage]")].slice(-1)[0]
        const props = lastNode[Object.keys(lastNode)[0]].child.memoizedProps
        text = props.message.text
        isDone = props.message.state == "complete"
        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.find(i => i.contentType == "text").content
        await setZoteroText()
      } else if (AI == "Copilot") {
        const lastAnwser = [...document.querySelectorAll('[data-content=ai-message]')].slice(-1)[0]
        type = "markdown"
        const props = lastAnwser[Object.keys(lastAnwser)[0]].pendingProps.children[1][0].props  
        text = props.item.text
        isDone = props.isStreamingComplete
        await setZoteroText()
      } else if (AI == "TencentDeepSeek") {
        const div = document.querySelector(".client-chat");
        const msg = div.__vue__.msgList.slice(-1)[0]
        isDone = msg.is_final
        const content = div.__vue__.msgList.slice(-1)[0].content
        if (!content) {
          text = "> " + div.__vue__.msgList.slice(-1)[0].agent_thought.procedures[0].debugging.content.trim().replace(/\n+/g, "\n")
        } else {
          text = content
        }
        type = "markdown"
        await setZoteroText()
      } else if (AI == "Xiaoyi") {
        const div = [...document.querySelectorAll(".receive-box")].slice(-1)[0];
        isDone = Boolean(div.closest(".msg-content")  && div.closest(".msg-content").querySelector(".tool-bar"))
        text = div.querySelector(".answer-cont").innerHTML
        type = "html"

        await setZoteroText()
      }
    }
  }
} catch (e) {
  console.log(e)
}
await sleep(100)

}
})();

Script Score
empty image
No one has rated the script yet. Be the first to rate it!