// ==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.5.0
// @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/*
// @match https://claude4.ai0.cn/*
// @match https://www.zaiwen.top/chat/*
// @match https://chat.aite.lol/*
// @match https://yuanbao.tencent.com/chat/*
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @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") {
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"
} else if (host == "www.zaiwen.top") {
AI = "Zaiwen"
} else if (host == 'yuanbao.tencent.com') {
AI = "Yuanbao"
} else if (host == "www.tiangong.cn") {
AI == "Tiangong"
}
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) {
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)) {
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 = ""
}
}
}
},
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: ""
},
{
AI: "Zaiwen",
regex: /admin\/chatbot$/,
extract: function (text) {
this.text = text
},
text: ""
},
{
AI: "Yuanbao",
regex: /api\/chat\/.+/,
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.type == "text") {
this.text += (data.msg || "")
}
} catch (e) { console.log("extract", e) }
}
}
},
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: "",
},
]
// 数据拦截,部分网站需要
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) {
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 ([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.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(".oeNDrlEA")
node[Object.keys(node)[1]].children[2].props.children[0].props.changeInput({ e: text })
await send(".dialogue_card_item", () => {
document.querySelector("#sendBtn").click()
})
} 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.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") {
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(".d7ad28f8 ", () => {
const button = document.querySelector('.c968d517');
button.click()
})
} 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()
})
} 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()
})
}
}
// 连续发送
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]",
},
Doubao: {
uploadMethod: "input",
selector: "input[type=file]",
},
Zaiwen: {
uploadMethod: "drag",
selector: ".arco-upload-draggable",
},
DeepSeek: {
uploadMethod: "drag",
selector: ".c52c3154",
},
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"
}
};
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) {
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) {
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 == "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.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)
}
})();