您需要先安装一个扩展,例如 篡改猴、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.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或关注我们的公众号极客氢云获取最新地址