// ==UserScript==
// @name Zotero GPT Connector
// @namespace http://tampermonkey.net/
// @version 2.4
// @description Zotero GPT Pro, support ChatGPT Gemini Poe Kimi Coze Chatglm
// @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/*
// @icon https://cdn.oaistatic.com/_next/static/media/favicon-32x32.be48395e.png
// @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 = ""
if (location.host == 'chatgpt.com') {
AI = "ChatGPT"
} else if (location.host == 'gemini.google.com') {
AI = "Gemini"
} else if (location.host == 'poe.com') {
AI = "Poe"
} else if (location.host == 'kimi.moonshot.cn') {
AI = "Kimi"
} else if (location.host == 'www.coze.com') {
AI = "Coze"
} else if (location.host == "chatglm.cn") {
AI = "Chatglm"
GM_cookie.list({
url: 'https://chatglm.cn/chatglm/feed-api/assistant_top/v4/recent_list' }, function (cookies, error) {
if (!error) {
localStorage.setItem("token", cookies.find(i => i.name == "chatglm_token").value)
}
});
}
// 在Zotero中执行代码
async function execInZotero(code) {
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'));
}
});
});
}
// 设定ChatGPT输入框文本并发送
const setText = (text) => {
const originalText = text
if (AI == "ChatGPT") {
// 获取 input 输入框的dom对象
var element_input = window.document.querySelector('#prompt-textarea');
// 修改input的值
element_input.value = text;
// 设置输入框的 input 事件
var event = new InputEvent('input', {
'bubbles': true,
'cancelable': true,
});
element_input.dispatchEvent(event);
const buttons = document.querySelector("#prompt-textarea").parentElement.parentElement.querySelectorAll("button");
const button = buttons[buttons.length - 1]
button.click()
setTimeout(() => {
button.click()
}, 100)
} else if (AI == "Gemini") {
// 获取 input 输入框的dom对象
var element_input = window.document.querySelector('rich-textarea .textarea');
// 修改input的值
element_input.textContent = text;
document.querySelector(".send-button").click();
setTimeout(() => {
document.querySelector(".send-button").click()
}, 100)
} else if (AI == "Poe") {
var element_input = window.document.querySelector('textarea[class*=GrowingTextArea_textArea]');
element_input.value = text;
// 设置输入框的 input 事件
var event = new InputEvent('input', {
'bubbles': true,
'cancelable': true,
});
element_input.dispatchEvent(event);
document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click();
setTimeout(() => {
document.querySelector("button[class*=ChatMessageSendButton_sendButton]").click()
}, 100)
} else if (AI == "Kimi") {
fetch(`https://kimi.moonshot.cn/api/chat/${location.href.match(/chat\/([0-9a-z]+)/)[1]}/completion/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Authorization": `Bearer ${localStorage.access_token}`
},
body: JSON.stringify({ "messages": [{ "role": "user", "content": text }], "refs": [], "use_search": false })
})
.then(response => {
if (response.status == 200) {
return response.body.getReader()
} else {
throw 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.tasks[window.Meet.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(/data: .+/g)) {
try {
const data = JSON.parse(line.split("data: ")[1])
if (data.event && data.event == "cmpl") {
text += data.text
} else if (data.error) {
text += data.error.message;
console.log(data);
document.querySelector("[data-testid=msh-sidebar-new]").click();
window.setTimeout(() => {
setText(originalText)
}, 1000)
return
}
} catch { }
execInZotero(`
let task = window.Meet.tasks[window.Meet.tasks.length-1]
task.responseText = ${JSON.stringify(text)};
task.type = "pending";
task.responseType = "markdown"
`)
}
} catch (e) {
console.log(e)
}
}
}, 0)
})
.catch(e => {
console.log(e)
window.setTimeout(async () => {
const res = await fetch("https://kimi.moonshot.cn/api/auth/token/refresh", {
headers: {
"Authorization": `Bearer ${localStorage.refresh_token}`
}
})
const data = await res.json()
localStorage.access_token = data.access_token
localStorage.refresh_token = data.refresh_token
setText(text)
})
})
} 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") {
fetch(`https://chatglm.cn/chatglm/backend-api/assistant/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"Authorization": `Bearer ${localStorage.token}`
},
body: JSON.stringify({
"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 }] }]
})
})
.then(response => {
if (response.status == 200) {
return response.body.getReader()
} else {
throw 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.tasks[window.Meet.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(/event:message\ndata: .+/g)) {
try {
const data = JSON.parse(line.split("data: ")[1])
if (localStorage.conversation_id.length == 0) {
localStorage.conversation_id = data.conversation_id
}
text = data.parts[0].content[0].text
} catch { }
execInZotero(`
let task = window.Meet.tasks[window.Meet.tasks.length-1]
task.responseText = ${JSON.stringify(text)};
task.type = "pending";
task.responseType = "markdown"
`)
}
} catch (e) {
console.log(e)
}
}
}, 0)
})
.catch(e => {
console.log(e)
localStorage.conversation_id = ""
location.reload()
})
}
}
// 阻塞
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
GM_registerMenuCommand('🔗 运行', () => {
isRunning = true
window.alert("🔗 已运行")
});
GM_registerMenuCommand('🎊 断开', () => {
isRunning = false
window.alert("🎊 断开")
});
// 通信
await sleep(3000)
while (true) {
if (!isRunning) {
await execInZotero(`
window.Meet.tasks = undefined
`)
await sleep(1000)
continue;
}
try {
const result = await execInZotero(`
if (!window.Meet.tasks){
window.Meet.tasks = []
} else {
window.Meet.tasks
}
`)
const tasks = result;
if (!tasks || tasks.length == 0) {
await sleep(500)
continue
}
const task = tasks.slice(-1)[0]
if (task.type == "pending") {
if (task.requestText) {
// 操作浏览器提问
if (AI == "ChatGPT") {
let getUserQuestionNum = () => document.querySelectorAll("[data-message-author-role=user]").length
const questionNum = getUserQuestionNum()
setText(task.requestText)
while (getUserQuestionNum() == questionNum) {
await sleep(100)
}
} else if (AI == "Gemini") {
let getUserQuestionNum = () => document.querySelectorAll("user-query").length
const questionNum = getUserQuestionNum()
setText(task.requestText)
while (getUserQuestionNum() == questionNum) {
await sleep(100)
}
while (document.querySelectorAll('model-response').length != getUserQuestionNum()) {
await sleep(100)
}
} else if (AI == "Poe") {
let getUserQuestionNum = () => document.querySelectorAll("[class*=ChatMessage_humanMessageWrapper]").length
const questionNum = getUserQuestionNum()
setText(task.requestText)
while (getUserQuestionNum() == questionNum) {
await sleep(100)
}
while (document.querySelectorAll('[class*=Message_botMessageBubble]').length != getUserQuestionNum()) {
await sleep(100)
}
} else if (AI == "Kimi") {
setText(task.requestText)
} else if (AI == "Coze") {
setText(task.requestText)
} else if (AI == "Chatglm") {
setText(task.requestText)
}
await execInZotero(`
let task = window.Meet.tasks[window.Meet.tasks.length-1]
task.requestText = "";
task.responseText = "<p>Answering (本过程不消耗Api Key额度)...</p>";
`)
} else {
let isDone = false, text = "", type = "html"
const setZoteroText = async () => {
await execInZotero(`
let task = window.Meet.tasks[window.Meet.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.tasks[window.Meet.tasks.length-1]
task.responseText = ${JSON.stringify(text)};
`)
}
}
if (AI == "ChatGPT") {
const outputEle = [...document.querySelectorAll('[data-testid^=conversation-turn]')].slice(-1)[0];
// const contentEle = outputEle.querySelector("div>div>div:nth-child(2)>div:nth-child(2)>div")
isDone = Boolean(outputEle.querySelector("span[data-state=closed]"))
text = outputEle.querySelector(".markdown").innerHTML
await setZoteroText()
} else if (AI == "Gemini") {
const outputEle = [...document.querySelectorAll('model-response')].slice(-1)[0];
const contentEle = outputEle.querySelector(".response-content message-content")
isDone = Boolean(outputEle.querySelector(".complete"))
text = contentEle.querySelector(".markdown").innerHTML
await setZoteroText()
} else if (AI == "Poe") {
const outputEle = [...document.querySelectorAll('[class*=Message_botMessageBubble]')].slice(-1)[0];
const contentEle = outputEle.querySelector("[class*=Markdown_markdownContainer]")
isDone = Boolean(document.querySelector("[class*=ChatMessageActionBar_actionBar]"))
text = contentEle.innerHTML
await setZoteroText()
} else if (AI == "Kimi") {
// 无需这一步
} 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 == "Kimi") {
// 无需这一步
}
}
}
} catch (e) {
console.log(e)
}
await sleep(100)
}
})();