您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm Yiyan Tongyi Claude Mytan ChanlderAi DeepSeek Doubao AIStudio
当前为
// ==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.3.2 // @author Polygon // @noframes // @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) { 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) && typeof (data.v) == "string") { this.p = streamPath this.text += data.v } else { this.p = "" } } } }, 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: /backend-api\/assistant\/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: "" } ] // 数据拦截,部分网站需要 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.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) { 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) { 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 (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) 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, 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 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") { 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("#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; await props.setInput(text); await sleep(100) 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 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: ".input-wrapper", until: () => { return !!!document.querySelector(".upload-wrapper") } }, Chatglm: { uploadMethod: "input", selector: "input[type=file]", }, }; 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); } }; /** * {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 execInZotero(` let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1] task.type = "done" `) await uploadFile(task.file.base64String, task.file.name) } 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(100) } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址