// ==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 3.9.1 // @author Polygon // @noframes // @match https://chatgpt.com/* // @match https://gemini.google.com/app* // @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 /.+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/* // @connect * // @connect https://kimi.moonshot.cn/* // @connect https://chatglm.cn/* // @connect https://chat.deepseek.com/* // @connect https://chatgpt.com/* // @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') { 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" } 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 == 'chat.qwenlm.ai') { 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" } const requestPatchArr = [ { AI: "Kimi", regex: /https:\/\/kimi.moonshot.cn\/api\/chat\/.+\/completion\/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]) if (data.event == "cmpl") { this.text += data.text } } } }, text: "" }, { AI: "AIStudio", regex: /GenerateContent$/, extract: function (text) { this._text += text this.text += text.match(/\[null,"([\s\S]*?)"\]/g).map(i => JSON.parse(i)[1]).join(""); console.log(this._text) }, text: "", _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) } } } console.log(resp, think) if (resp == "") { if (think != "") { this.text = ">" + think.replace(/\n/g, "n>") } } else { this.text = resp } }, text: "" }, { AI: "DeepSeek", regex: /api\/v0\/chat\/completion$/, extract: function (text) { console.log(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 { this.text += (data.choices[0].delta.content || "") } catch (e) { console.log("extract", e) } } } }, 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]+```/, "").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: "", }, ] // 数据拦截,部分网站需要 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 { 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 ([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 => { 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) 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"; `); requestPatch.text = "" }); } // 开始处理流 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 => `
${i}
`).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 node = document.querySelector("[class^=inputInner]") // await node[Object.keys(node)[1]].children[1][0].ref.current.insertText(text) 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") { const node = document.querySelector(".chatInput--eJzBH8LP") await node[Object.keys(node)[1]].children[1].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.setPrompt(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") { 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() }) } } // 连续发送 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", // until: () => { // return !!!document.querySelector(".upload-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]", }, }; if (!AIData[AI]) { AIData[AI] = { 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)); } // 支持:多个联动页面打开 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(` 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 || 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 = "Waiting ${AI}...
"; `) } 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").querySelector(".tool-bar")) text = div.querySelector("[class^=markdown-container]").innerHTML type = "html" await setZoteroText() } } } } catch (e) { console.log(e) } await sleep(100) } })();