Zotero GPT Pro: Supports virtually all the AI platforms you know.
Size
57.3 KB
Version
4.4.8
Created
Dec 1, 2025
Updated
11 days ago
1// ==UserScript==
2// @name Zotero GPT Connector
3// @description Zotero GPT Pro: Supports virtually all the AI platforms you know.
4// @namespace http://tampermonkey.net/
5// @icon https://github.com/MuiseDestiny/zotero-gpt/blob/bootstrap/addon/chrome/content/icons/favicon.png?raw=true
6// @noframes
7// @version 4.4.8
8// @author Polygon
9// @match https://chatgpt.com/*
10// @match https://gemini.google.com/*
11// @match https://poe.com/*
12// @match https://kimi.moonshot.cn/*
13// @match https://chatglm.cn/*
14// @match https://yiyan.baidu.com/*
15// @match https://qianwen.aliyun.com/*
16// @match https://claude.ai/*
17// @match https://mytan.maiseed.com.cn/*
18// @match https://mychandler.bet/*
19// @match https://chat.deepseek.com/*
20// @match https://www.doubao.com/chat/*
21// @match https://*.chatshare.biz/*
22// @match https://chat.kelaode.ai/*
23// @match https://chat.rawchat.cn/*
24// @match https://chat.sharedchat.*/*
25// @match https://node.dawuai.buzz/*
26// @match https://aistudio.google.com/*
27// @match https://claude.ai0.cn/*
28// @match https://grok.com/*
29// @match https://china.aikeji.vip/*
30// @match https://chatgtp.chat/*
31// @match https://iai.aichatos8.com.cn/*
32// @match https://share.mosha.cloud/*
33// @match /.+gpt2share.+/
34// @include /.+rawchat.+/
35// @include /.+kimi.+/
36// @include /.+freeoai.+/
37// @include /.+chatgpt.+/
38// @include /.+claude.+/
39// @include /.+qwen.+/
40// @include /.+coze.+/
41// @include /.+grok.+/
42// @include /.+tongyi.+/
43// @include /.+chatopens.+/
44// @include /.+kelaode.+/
45// @match https://github.com/copilot/*
46// @match https://shareai.cfd/*
47// @match https://lmarena.ai/*
48// @match https://*.mjpic.cc/*
49// @match https://chat.minimaxi.com/*
50// @match https://*.mjpic.cc/*
51// @match https://www.zaiwen.top/chat/*
52// @match https://chat.aite.lol/*
53// @match https://yuanbao.tencent.com/chat/*
54// @match https://chatgptup.com/*
55// @match https://ihe5u7.aitianhu2.top/*
56// @match https://cc01.plusai.io/*
57// @match https://arc.aizex.me/*
58// @match https://www.chatwb.com/*
59// @match https://www.xixichat.top/*
60// @match https://zchat.tech/*
61// @match https://*.sorryios.*/*
62// @match https://monica.im/*
63// @match https://copilot.microsoft.com/*
64// @match https://gptsdd.com/*
65// @match https://max.bpjgpt.top/*
66// @match https://nbai.tech/
67// @match https://x.liaobots.work/*
68// @match https://x.liaox.ai/*
69// @match https://chat.qwenlm.ai/*
70// @match https://lke.cloud.tencent.com/*
71// @match https://dazi.co/*
72// @match https://www.wenxiaobai.com/*
73// @match https://www.techopens.com/*
74// @match https://xiaoyi.huawei.com/*
75// @match https://chat.baidu.com/*
76// @match https://qrms.com/*
77// @match https://www.perplexity.ai/*
78// @match https://sider.ai/*
79// @match https://saas.ai1.bar/*
80// @match https://sx.xiaoai.shop/*
81// @match https://oai.liuliangbang.vip/*
82// @match https://*.dftianyi.com/*
83// @match https://notebooklm.google.com/notebook/*
84// @match https://chat.bpjgpt.top/*
85// @match https://*.plusai.io/*
86// @match https://*.plusai.me/*
87// @match https://*.yrai.cc/*
88// @connect *
89// @connect https://kimi.moonshot.cn/*
90// @connect https://chatglm.cn/*
91// @connect https://chat.deepseek.com/*
92// @connect https://chatgpt.com/*
93// @connect http://127.0.0.1:23119/zoterogpt
94// @grant GM_xmlhttpRequest
95// @grant GM_registerMenuCommand
96// @grant GM_setValue
97// @grant GM_getValue
98// @grant unsafeWindow
99// @run-at document-start
100// @downloadURL https://update.greasyfork.org/scripts/480616/Zotero%20GPT%20Connector.user.js
101// @updateURL https://update.greasyfork.org/scripts/480616/Zotero%20GPT%20Connector.meta.js
102// ==/UserScript==
103
104(async function () {
105 'use strict';
106 let isRunning = true
107 let AI = "ChatGPT"
108 const host = location.host
109 if (host == 'chatgpt.com') {
110 AI = "ChatGPT"
111 } else if (host == 'gemini.google.com') {
112 AI = "Gemini"
113 } else if (host == 'poe.com') {
114 AI = "Poe"
115 } else if (host == 'kimi.moonshot.cn' || host == 'www.kimi.com') {
116 AI = "Kimi"
117 } else if (host.includes('coze')) {
118 AI = "Coze"
119 } else if (host == "chatglm.cn") {
120 AI = "Chatglm"
121 } else if (host == 'yiyan.baidu.com') {
122 AI = "Yiyan"
123 } else if (host.includes('tongyi')) {
124 AI = "Tongyi"
125 } else if (["claude", "kelaode", "yrai", "aikeji", "gpt2share"].find(i=>host.includes(i)) ) {
126 AI = "Claude"
127 } else if (host == 'mytan.maiseed.com.cn') {
128 AI = "MyTan"
129 } else if (host == 'mychandler.bet') {
130 AI = "ChanlderAi"
131 } else if (host == 'chat.deepseek.com') {
132 AI = "DeepSeek"
133 } else if (host == "www.doubao.com") {
134 AI = "Doubao"
135 } else if (host == 'aistudio.google.com') {
136 AI = "AIStudio"
137 } else if (host == "www.zaiwen.top") {
138 AI = "Zaiwen"
139 } else if (host == 'yuanbao.tencent.com') {
140 AI = "Yuanbao"
141 } else if (host == "www.tiangong.cn") {
142 AI == "Tiangong"
143 } else if (host == 'monica.im') {
144 AI = "Monica"
145 } else if (host == 'copilot.microsoft.com') {
146 AI = "Copilot"
147 } else if (location.host.includes('qwen')) {
148 AI = "Qwen"
149 } else if (location.host == 'lke.cloud.tencent.com') {
150 AI = "TencentDeepSeek"
151 } else if (location.host == 'dazi.co') {
152 AI = "AskManyAI"
153 } else if (location.host == 'www.wenxiaobai.com') {
154 AI = "Wenxiaobai"
155 } else if (location.host.includes('grok')) {
156 AI = "Grok"
157 } else if (location.host == 'xiaoyi.huawei.com') {
158 AI = "Xiaoyi"
159 } else if (location.host == 'chat.baidu.com') {
160 AI = "Baidu"
161 } else if (location.host == 'www.perplexity.ai') {
162 AI = "Perplexity"
163 } else if (location.host == 'sider.ai') {
164 AI = "Sider"
165 } else if (host == 'notebooklm.google.com') {
166 AI = "GoogleNotebookLM"
167 } else if (host == 'chat.minimaxi.com') {
168 AI = "MinMax"
169 } else if (host == 'lmarena.ai') {
170 AI = "LMArena"
171 } else if (host == 'github.com') {
172 AI = "GitHubCopilot"
173 }
174
175 const getOutputText = (resp = "", think= "") => {
176 let text = ""
177 if (think) {
178 text += ("<think>" + think)
179 if (resp) { text += "</think>"}
180 }
181 text += resp
182 return text
183 }
184 const requestPatchArr = [
185 {
186 AI: "Kimi",
187 regex: /ChatService\/Chat/,
188 extract: function (text, allText) {
189 console.log(allText)
190 let think="", resp = ""
191 const arr = allText.split(/\x00\x00\x00\x00[^\{]+/).filter(Boolean).map(i=>{
192 try{return JSON.parse(i)} catch {return {}}
193 })
194 for (let data of arr) {
195 if (data.op != "append") {continue}
196 if (data.mask == "block.think.content") {
197 think += data.block.think.content || ""
198 } else if (data.mask == "block.text.content") {
199 resp += data.block.text.content || ""
200 }
201 }
202 this.text = getOutputText(resp, think)
203 },
204 text: ""
205 },
206 {
207 AI: "Tongyi",
208 regex: /dialog\/conversation/,
209 extract: function (text, allText) {
210 let think = "", resp = ""
211 for (let s of allText.split("\n")) {
212 try {
213 if (!s || !s.startsWith("data: {")) { continue }
214 const data = JSON.parse(s.slice(5))
215 if (data.contents[0].contentType == "text") {
216 if (data.contents[0].incremental) {
217 resp += data.contents[0].content || ""
218 } else {
219 resp = data.contents[0].content || ""
220 }
221 } else if (data.contents[0].contentType == "think") {
222 think += JSON.parse(data.contents[0].content).content || ""
223 }
224 } catch (e) {console.log(e)}
225 }
226 this.text = getOutputText(resp, think)
227 },
228 text: ""
229 },
230 {
231 AI: "AIStudio",
232 regex: /GenerateContent$/,
233 extract: function (text) {
234 let data
235 while (!data) {
236 try {
237 data = JSON.parse(text)
238 }catch {
239 text += "]"
240 }
241 }
242 console.log(data)
243 let think = "", resp = ""
244 for (let i of data[0]) {
245 try {
246 let s = i[0][0][0][0][0][1]
247 if (i[0][0][0][0][0][12]) {
248 think += s
249 } else {
250 resp += s
251 }
252 } catch {}
253 }
254 this.text = getOutputText(resp, think)
255 },
256 text: "",
257 },
258 {
259 AI: "ChatGPT",
260 regex: /conversation$/,
261 extract: function (text) {
262 for (let line of text.split("\n")) {
263 console.log("line", line)
264 if (line.startsWith('data: {"message')) {
265 try { JSON.parse(line.split("data: ")[1]) } catch { continue }
266 const data = JSON.parse(line.split("data: ")[1])
267 if (data.message.content.content_type == "text") {
268 this.text = data.message.content.parts[0]
269 }
270 } else if (line.startsWith("data: {")) {
271 try { JSON.parse(line.split("data: ")[1]) } catch { continue }
272 const data = JSON.parse(line.split("data: ")[1])
273 console.log(data, this.p)
274 const streamPath = "/message/content/parts/0"
275 if (Object.keys(data).length == 1 && typeof (data.v) == "string" && this.p == streamPath) {
276 this.text += data.v
277 console.log("text", this.text)
278 } else if ((this.p == streamPath || data.p == streamPath)) {
279 this.p = streamPath
280 if (data.o && data.o == "add") {
281 this.text = ""
282 }
283 if (typeof (data.v) == "string") {
284 this.text += data.v
285 } else if (Array.isArray(data.v)) {
286 const d = data.v.find(i => i.p == streamPath)
287 if (d && typeof (d.v) == "string") {
288 this.text += d.v
289 }
290 }
291 } else {
292 this.p = ""
293 }
294 } else if (line.startsWith("data: [")) {
295 try {
296 const delta = JSON.parse(line.replace(/^data:\s/, "")).slice(-1)[0]
297 if (typeof (delta) == "string" ) {
298 this.text += delta
299 }
300 } catch {}
301 }
302 }
303 },
304 p: "",
305 text: ""
306 },
307 {
308 AI: "Claude",
309 regex: /chat_conversations\/.+\/completion/,
310 extract: function (text) {
311 for (let line of text.split("\n")) {
312 if (line.startsWith("data: {")) {
313 try { JSON.parse(line.split("data: ")[1]) } catch { continue }
314 const data = JSON.parse(line.split("data: ")[1])
315 if (data.type && data.type == "completion") {
316 this.text += data.completion || ""
317 } else if (data.type && data.type == "content_block_delta") {
318 this.text += data.delta.text || ""
319 }
320 }
321 }
322 },
323 text: ""
324 },
325 {
326 AI: "Chatglm",
327 regex: /stream/,
328 extract: function (text) {
329 for (let line of text.split("\n")) {
330 if (line.startsWith("data:")) {
331 try { JSON.parse(line.split("data:")[1]) } catch { continue }
332 const data = JSON.parse(line.split("data:")[1])
333 try {
334 if (data.parts && data.parts[0] && data.parts[0].content[0].type == "text") {
335 this.text = data.parts[0].content[0].text
336 }
337 } catch (e) { console.log("extract", e) }
338 }
339 }
340 },
341 text: ""
342 },
343 {
344 AI: "Zaiwen",
345 regex: /admin\/chatbot$/,
346 extract: function (text) {
347 this.text = text
348
349 },
350 text: ""
351 },
352 {
353 AI: "Yuanbao",
354 regex: /api\/chat\/.+/,
355 extract: function (allText) {
356 let think = "", resp = ""
357 for (let line of allText.split("\n")) {
358 if (line.startsWith("data: {")) {
359 try { JSON.parse(line.split("data: ")[1]) } catch { continue }
360 const data = JSON.parse(line.split("data: ")[1])
361 try {
362 if (data.type == "text") {
363 resp += (data.msg || "")
364 } else if (data.type == "think") {
365 think += (data.content || "")
366 } else if (data.type == "replace") {
367 resp += `\n${data.replace.multimedias[0].desc}`
368 }
369 } catch (e) { console.log("extract", e) }
370 }
371 }
372 this.text = getOutputText(resp, think)
373 },
374 text: ""
375 },
376 {
377 AI: "DeepSeek",
378 regex: /completion$/,
379 extract: function (text) {
380 let resp = "", think = ""
381 for (let line of text.split("\n")) {
382 if (line.startsWith("data: {")) {
383 try { JSON.parse(line.split("data: ")[1]) } catch { continue }
384 const data = JSON.parse(line.split("data: ")[1])
385 try {
386 if (data.choices) {
387 if (data.choices[0].delta.type == "thinking") {
388 think += (data.choices[0].delta.content || "")
389 } else if (data.choices[0].delta.type == "text") {
390 resp += (data.choices[0].delta.content || "")
391 }
392 } else {
393 if (Array.isArray(data.v) && data.v[0].type) {
394 this.type = data.v[0].type
395 data.v = data.v[0].content
396 }
397 if (typeof (data.v) == "string") {
398 if (this.type == "THINK") {
399 think += data.v || ""
400 } else if (this.type == "RESPONSE") {
401 resp += data.v || ""
402 }
403 }
404 }
405 } catch (e) { console.log("extract", e) }
406 }
407 }
408 this.text = getOutputText(resp, think)
409 },
410 text: "",
411 },
412 {
413 AI: "ChanlderAi",
414 regex: /api\/chat\/Chat$/,
415 extract: function (text) {
416 for (let line of text.split("\n")) {
417 console.log("line", line)
418 if (line.startsWith("data:{")) {
419 try { JSON.parse(line.split("data:")[1]) } catch { continue }
420 const data = JSON.parse(line.split("data:")[1])
421 try {
422 this.text += data.delta
423 } catch (e) { console.log("extract", e) }
424 }
425 }
426
427 },
428 text: ""
429 },
430 {
431 AI: "Yiyan",
432 regex: /chat\/conversation\/v2$/,
433 extract: function (text, allText) {
434 let delta = ""
435 for (let line of allText.split("\n\nevent:message\n")) {
436 if (line.startsWith("data:{")) {
437 try { JSON.parse(line.split("data:")[1]) } catch { continue }
438 const data = JSON.parse(line.split("data:")[1])
439 try {
440 delta += data.data.text || ""
441 } catch (e) { console.log("extract", e) }
442 }
443 }
444 this.text = delta
445 },
446 text: "",
447 },
448 {
449 AI: "Doubao",
450 regex: /samantha\/chat\/completion/,
451 extract: function (text, allText) {
452 let think = "", resp = ""
453 for (let line of allText.split("\n")) {
454 if (line.startsWith("data:")) {
455 try {
456 const data = JSON.parse(JSON.parse(line.slice(5)).event_data)
457 const data1 = JSON.parse(data.message.content)
458
459 if (![1, 5, 6].includes(data1.type) ) {
460 if (data1.text) {
461 resp += data1.text
462 } else if (data1.think) {
463 think += data1.think
464 }
465 }
466 } catch {}
467 }
468 }
469 this.text = getOutputText(resp, think)
470 },
471 text: "",
472 },
473 {
474 AI: "Monica",
475 regex: /api.monica.im\/api\/custom_bot\/chat/,
476 extract: function (text, allText) {
477 let think = "", resp = ""
478 for (let line of allText.split("\n")) {
479 if (line.startsWith("data:")) {
480 const data = JSON.parse(line.slice(5))
481 if (Boolean(data.agent_status) && Boolean(data.agent_status.type == "thinking_detail_stream")) {
482 think += (data.agent_status.metadata.reasoning_detail || "")
483 }
484 resp += data.text
485 }
486 }
487 this.text = getOutputText(resp, think)
488 },
489 text: "",
490 },
491 {
492 AI: "Qwen",
493 regex: /chat\/completions/,
494 extract: function (text, allText) {
495 let think = "", resp = ""
496 for (let line of allText.split("\n")) {
497 if (line.startsWith("data: {")) {
498 try { JSON.parse(line.split("data:")[1]) } catch { continue }
499 const data = JSON.parse(line.split("data:")[1])
500 try {
501 if (data.choices[0].delta.phase == "think") {
502 think += data.choices[0].delta.content
503 } else if (data.choices[0].delta.phase == "answer") {
504 resp += data.choices[0].delta.content
505 }
506 } catch (e) { console.log("extract", e) }
507 }
508 }
509 this.text = getOutputText(resp, think)
510
511 },
512 text: "",
513 },
514 {
515 AI: "AskManyAI",
516 regex: /engine\/sseQuery/,
517 extract: function (text, allText) {
518 let think = "", resp = ""
519 for (let line of allText.split("\n")) {
520 if (line.startsWith("data: {")) {
521 try { JSON.parse(line.split("data:")[1]) } catch { continue }
522 const data = JSON.parse(line.split("data:")[1])
523 try {
524 if (data.content.startsWith("[HIT-REF]")) { continue}
525 if (data.event == "thinking") {
526 think += data.content
527 } else if (data.event == "resp") {
528 resp += data.content
529 }
530 } catch (e) { console.log("extract", e) }
531 }
532 }
533 this.text = getOutputText(resp, think)
534
535 },
536 text: "",
537 },
538 {
539 AI: "Wenxiaobai",
540 regex: /conversation\/chat\/v\d$/,
541 extract: function (_, allText) {
542 if (!allText) { return }
543 let resp = ""
544 for (let line of allText.replace(/event:message\ndata/g, "message").split("\n")) {
545 if (line.startsWith("message:{")) {
546 try { JSON.parse(line.split("message:")[1]) } catch { continue }
547 const data = JSON.parse(line.split("message:")[1])
548 try {
549 resp += data.content || ""
550 } catch (e) { console.log("extract", e) }
551 }
552 }
553 console.log(resp)
554 resp = resp.replace(/^```ys_think[\s\S]+?\n\n```\n/, "").replace(/[\s\S]+?```ys_think/, "```ys_think")
555 if (resp.includes("```ys_think")) {
556 resp = ">"+resp.split("\n").slice(3).join("\n>")
557 }
558 this.text = resp
559 },
560 text: "",
561 },
562 {
563 AI: "Coze",
564 regex: /conversation\/chat/,
565 extract: function (text, allText) {
566 for (let line of text.split("\n")) {
567 if (line.startsWith("data:{")) {
568 try { JSON.parse(line.split("data:")[1]) } catch { continue }
569 const data = JSON.parse(line.split("data:")[1])
570 try {
571 if (data.message.type == "answer") {
572
573 this.text += data.message.content || ""
574 }
575 } catch (e) { console.log("extract", e) }
576 }
577 }
578 },
579 text: "",
580 },
581 {
582 AI: "Grok",
583 regex: /(conversations\/new|responses)$/,
584 extract: function (text, allText) {
585 console.log(text)
586 let resp="", think=""
587 for (let t of allText.split("\n") ) {
588 let data
589 try {
590 data = JSON.parse(t).result
591 if (data.response) {data = data.response}
592 if (data.isThinking) {
593 think += data.token || ""
594 } else{
595 resp += data.token || ""
596 }
597 } catch {continue}
598 }
599 this.text = getOutputText(resp, think)
600
601 },
602 text: "",
603 },
604 {
605 AI: "Baidu",
606 regex: /conversation$/,
607 extract: function (text, allText) {
608 let resp = "", think = ""
609 for (let t of allText.split("\n")) {
610 if (!t.startsWith("data:")) {continue}
611 let data
612 console.log(t.slice(5))
613 try {
614 data = JSON.parse(t.slice(5)).data
615 } catch { continue }
616 console.log(data)
617 if (!data) { continue}
618 if (data.message.metaData.state == "generating-resp") {
619 if (data.message.content.generator.component == "reasoningContent") {
620 think += data.message.content.generator.data.value || ""
621 } else if (data.message.content.generator.component == "markdown-yiyan"){
622 resp += data.message.content.generator.data.value || ""
623 }
624 }
625 }
626 this.text = getOutputText(resp, think)
627
628 },
629 text: "",
630 },
631 {
632 AI: "MyTan",
633 regex: /messages$/,
634 extract: function (text, allText) {
635 for (let line of text.split("\n")) {
636 if (line.startsWith("data:")) {
637 try { JSON.parse(line.split("data:")[1]) } catch { continue }
638 const data = JSON.parse(line.split("data:")[1])
639 try {
640 this.text += data.choices[0].delta.content
641
642 } catch (e) { console.log("extract", e) }
643 }
644 }
645 },
646 text: "",
647 },
648 {
649 AI: "Perplexity",
650 regex: /perplexity_ask$/,
651 extract: function (text, allText) {
652 let think = "", resp = ""
653 for (let line of text.split("\n")) {
654 if (line.startsWith("data:")) {
655 try { JSON.parse(line.split("data:")[1]) } catch { continue }
656 const data = JSON.parse(line.split("data:")[1])
657 try {
658 if (data.blocks) {
659 for (let block of data.blocks) {
660 console.log(data.blocks)
661 if (block.intended_usage == "ask_text"){
662 if (block.markdown_block && block.markdown_block.answer) {
663 resp = block.markdown_block.answer || ""
664 } else if (block.diff_block && block.diff_block.field == "markdown_block") {
665 for (let patch of block.diff_block.patches){
666 if (patch.op == "replace" && patch.path == "/answer") {
667 resp = patch.value || ""
668 }
669 }
670 }
671 }
672 }
673 }
674
675 } catch (e) { console.log("extract", e) }
676 }
677 }
678 this.text = getOutputText(resp, think)
679
680 },
681 text: "",
682 },
683 {
684 AI: "Sider",
685 regex: /(completions|chat\/wisebase)/,
686 extract: function (text, allText) {
687 let think = "", resp = ""
688 for (let line of allText.split("\n")) {
689 if (line.startsWith("data:")) {
690 try { JSON.parse(line.split("data:")[1]) } catch { continue }
691 const data = JSON.parse(line.split("data:")[1])
692 try {
693 console.log(data)
694 if (data.data.type == "reasoning_content") {
695 think += data.data.reasoning_content.text || ""
696 } else if (data.data.type == "text") {
697 resp += data.data.text || ""
698 }
699 } catch (e) { console.log("extract", e) }
700 }
701 }
702 this.text = getOutputText(resp, think)
703
704 },
705 text: "",
706 },
707 {
708 AI: "Gemini",
709 regex: /BardFrontendService\/StreamGenerate/,
710 extract: function (text) {
711 let think = "", resp = ""
712 for (let line of text.split(/\n\d+\n/)) {
713 try {
714 const data = JSON.parse(line)
715 if (data[0][0] == "wrb.fr") {
716 const data1 = JSON.parse(data[0][2])[4][0]
717 resp = data1[1][0]
718 think = data1[37][0][0]
719 }
720 } catch {}
721 }
722 this.text = getOutputText(resp.replace(/\[cite.+?\]/g, ""), think)
723
724 },
725 text: "",
726 },
727 {
728 AI: "GoogleNotebookLM",
729 regex: /data\/batchexecute/,
730 extract: function (text) {
731 let think = "", resp = ""
732 for (let line of text.split(/\n\d+\n/)) {
733 console.log(line)
734 try {
735 const data = JSON.parse(line)
736 console.log(data)
737 if (data[0][0] == "wrb.fr") {
738 const data1 = JSON.parse(data[0][2])
739 console.log(data1)
740
741 resp = data1[0][0]
742 // think = data1[37][0][0]
743 }
744 } catch { }
745 }
746 this.text = getOutputText(resp, think)
747
748 },
749 text: "",
750 },
751 {
752 AI: "MinMax",
753 regex: /api\/chat\/msg/,
754 extract: function (text) {
755 let resp = "", think = ""
756
757 try {
758 const s = text.split("\n").find(i => i.startsWith("data:"))
759 const data = JSON.parse(s.slice(5))
760 const content = data.data.messageResult.content
761 if (content.startsWith("<think>")) {
762 if (content.includes("</think>")) {
763 const res = content.match(/<think>([\s\S]*?)<\/think>([\s\S]*)/)
764 think = res[1] || ""
765 resp = res[2] || ""
766 } else {
767 think = content.replace(/^<think>/, "")
768 }
769 } else {
770 resp = content
771 }
772 } catch (e) {
773 console.log("error", e,text)
774 }
775 this.text = getOutputText(resp, think)
776 },
777 text: "",
778 },
779 {
780 AI: "LMArena",
781 regex: /stream\/post-to-evaluation/,
782 extract: function (text) {
783 try {
784 for (let line of text.split("\n")) {
785 if (line.startsWith("a0:")) {
786 this.text += JSON.parse(line.slice(3))
787 }
788 }
789
790 } catch (e) {
791 console.log("error", e, text)
792 }
793 this.text = getOutputText(this.text)
794 },
795 text: "",
796 },
797 {
798 AI: "GitHubCopilot",
799 regex: /github\/chat\/threads\/.+\/messages/,
800 extract: function (text) {
801 try {
802 for (let line of text.split("\n")) {
803 if (line.startsWith("data:")) {
804 const data = JSON.parse(line.slice(5))
805 if (data.type == "content") {
806 this.text += data.body
807 }
808 }
809 }
810 } catch (e) {
811 console.log("error", e, text)
812 }
813 this.text = getOutputText(this.text)
814 },
815 text: "",
816 },
817 ]
818
819 // 数据拦截,部分网站需要
820 const originalXhrOpen = XMLHttpRequest.prototype.open;
821 XMLHttpRequest.prototype.open = function (method, url, async) {
822 this.addEventListener('readystatechange', async function () {
823 let requestPatch
824 if ((requestPatch = requestPatchArr.find(i => i.AI == AI && i.regex.test(url)))) {
825 execInZotero(`
826 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
827 task.responseText = ${JSON.stringify(requestPatch.text || "")};
828 task.responseType = "markdown";
829 `);
830 if (this.readyState === 3) {
831 try {
832 requestPatch.extract(this.responseText)
833 } catch(e) {
834 console.log("error extract", e, this.responseText)
835 }
836 await execInZotero(`
837 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
838 task.responseText = ${JSON.stringify(requestPatch.text || "")};
839 task.type = "pending";
840 task.responseType = "markdown"
841 `)
842 } else if ([0, 4].includes(this.readyState)) {
843 await execInZotero(`
844 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
845 task.responseText = ${JSON.stringify(requestPatch.text || "")};
846 task.type = "done";
847 task.responseType = "markdown"
848 `)
849 requestPatch.text = ""
850 }
851 }
852 });
853 originalXhrOpen.apply(this, arguments);
854 };
855
856
857 const originalFetch = window.fetch;
858 unsafeWindow.fetch = async function () {
859 // console.log("fetch")
860 const response = await originalFetch.apply(this, arguments);
861 let clonedResponse = response.clone();
862 window.setTimeout(async () => {
863 const url = response.url
864 const requestPatch = requestPatchArr.find(i => i.AI == AI && i.regex.test(url))
865 if (requestPatch) {
866 requestPatch.text = ""
867 execInZotero(`
868 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
869 task.responseText = ${JSON.stringify(requestPatch.text)};
870 task.responseType = "markdown";
871 `);
872 console.log("requestPatch", requestPatch)
873 console.log(clonedResponse)
874 const reader = clonedResponse.body.getReader();
875 const decoder = new TextDecoder()
876 let allText = ""
877 function processStream() {
878 reader.read().then(({ done, value }) => {
879 if (done) {
880 console.log("requestPatch.text", requestPatch.text)
881 // if (requestPatch.text.length > 0) {
882 execInZotero(`
883 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
884 task.responseText = ${JSON.stringify(requestPatch.text || "")};
885 task.type = "done";
886 task.responseType = "markdown";
887 `);
888 requestPatch.text = ""
889 // }
890 return;
891 }
892
893 // 将 Uint8Array 转为字符串
894 const text = decoder.decode(value, { stream: true });
895 allText += text
896 try {
897 requestPatch.extract(text, allText)
898 } catch (e) { console.log("requestPatch.extract(text)", e) }
899 execInZotero(`
900 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
901 task.responseText = ${JSON.stringify(requestPatch.text || "")};
902 task.responseType = "markdown";
903 `);
904
905 // 递归调用,继续读取流数据
906 processStream();
907 }).catch(error => {
908 // 捕获所有错误,包括 AbortError
909 console.log("Error when Patch", error)
910 execInZotero(`
911 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length - 1];
912 task.responseText = ${JSON.stringify(requestPatch.text || "")};
913 task.type = "done";
914 task.responseType = "markdown";
915 `);
916 requestPatch.text = ""
917 });
918 }
919
920 // 开始处理流
921 processStream();
922 clonedResponse = null
923 }
924 })
925 return response
926 };
927
928
929 // 在Zotero中执行代码
930 async function execInZotero(code) {
931 code = `
932 if (!window.Meet.Connector){
933 window.Meet.Connector = ${JSON.stringify({
934 AI, time: Date.now() / 1e3, tasks: []
935 })};
936 } else {
937 window.Meet.Connector.time = ${Date.now() / 1e3};
938 window.Meet.Connector.AI = "${AI}";
939 }
940 ${code}`
941 try {
942 return new Promise((resolve, reject) => {
943 GM_xmlhttpRequest({
944 method: "POST",
945 url: "http://127.0.0.1:23119/zoterogpt",
946 headers: {
947 "Content-Type": "application/json",
948 },
949 responseType: "json",
950 data: JSON.stringify({ code }),
951 onload: function (response) {
952 if (response.status >= 200 && response.status < 300) {
953 resolve(response.response.result);
954 } else {
955 reject(new Error(`Request failed with status: ${response.status}`));
956 }
957 },
958 onerror: function (error) {
959 reject(new Error('Network error'));
960 }
961 });
962 });
963 } catch (e) {
964 window.alert("execInZotero error: " + code);
965 return ""
966 }
967 }
968 return 1
969 // 设定ChatGPT输入框文本并发送
970 const setText = async (text) => {
971 const dispatchInput = (selector) => {
972 // 获取 input 输入框的dom对象
973 var inputNode = document.querySelector(selector);
974 if (!inputNode) { return }
975 // 修改input的值
976
977 inputNode.value = text;
978 // plus
979 try {
980 inputNode.innerHTML = text.split("\n").map(i => `<p>${i}</p>`).join("\n");
981 } catch { }
982 // 设置输入框的 input 事件
983 var event = new InputEvent('input', {
984 'bubbles': true,
985 'cancelable': true,
986 });
987 inputNode.dispatchEvent(event);
988 }
989 const setTextareaValue = async (textarea) => {
990 const props = Object.values(textarea)[1]
991
992 // 获取目标 DOM 节点(假设 temp2 是 DOM 元素引用)
993 const targetElement = textarea;
994
995 // 创建伪事件对象
996 const e = {
997 target: targetElement,
998 currentTarget: targetElement,
999 type: 'change',
1000 };
1001
1002 // 手动设置值(需同时更新 DOM 和 React 状态)
1003 targetElement.value = text;
1004
1005 // 触发 React 的 onChange 处理
1006 await props.onChange(e);
1007 }
1008 const setEditorText = async () => {
1009 const editor = document.querySelector('[data-lexical-editor][role=textbox]').__lexicalEditor
1010 await editor.setEditorState(
1011 editor.parseEditorState(
1012 {
1013 "root": {
1014 "children": [
1015 {
1016 "children": [
1017 {
1018 "detail": 0,
1019 "format": 0,
1020 "mode": "normal",
1021 "style": "",
1022 "text": text,
1023 "type": "text",
1024 "version": 1
1025 }
1026 ],
1027 "direction": null,
1028 "format": "",
1029 "indent": 0,
1030 "type": "paragraph",
1031 "version": 1,
1032 "textFormat": 0
1033 }
1034 ],
1035 "direction": null,
1036 "format": "",
1037 "indent": 0,
1038 "type": "root",
1039 "version": 1
1040 }
1041 }
1042 )
1043 )
1044 }
1045 if (AI == "ChatGPT") {
1046 dispatchInput('#prompt-textarea')
1047 await sleep(100)
1048 await send("article", () => {
1049 const button = document.querySelector('[data-testid="send-button"]');
1050 button.click()
1051 })
1052 } else if (AI == "Gemini") {
1053 // 获取 input 输入框的dom对象
1054 const element_input = document.querySelector('rich-textarea .textarea');
1055 if (!element_input) {
1056 return
1057 }
1058 // 修改input的值
1059 element_input.textContent = text;
1060 await sleep(100)
1061 await send(".conversation-container", () => {
1062 const button = document.querySelector('.send-button');
1063 button.click()
1064 })
1065 } else if (AI == "Poe") {
1066 dispatchInput('textarea[class*=GrowingTextArea_textArea]')
1067 await send("[class^=Message_selectableText]", () => {
1068 const button = document.querySelector('[data-button-send=true]');
1069 button.click()
1070 })
1071 } else if (AI == "Kimi") {
1072 await setEditorText()
1073 await send(".chat-content-item", () => {
1074 const button = document.querySelector('.send-button');
1075 button.click()
1076 })
1077
1078 } else if (AI == "Coze") {
1079 const textarea = document.querySelector("textarea.rc-textarea")
1080 await setTextareaValue(textarea)
1081 await sleep(100)
1082 await send("[data-message-id]", () => {
1083 const button = document.querySelector('button[data-testid="bot-home-chart-send-button"]');
1084 button.click()
1085 })
1086 } else if (AI == "Chatglm") {
1087 dispatchInput(".input-box-inner textarea")
1088 await send(".item.conversation-item", () => {
1089 const button = document.querySelector('.enter img');
1090 if (button) {
1091 const mouseDownEvent = new MouseEvent('mousedown', {
1092 bubbles: true,
1093 cancelable: true
1094 });
1095 button.dispatchEvent(mouseDownEvent);
1096 }
1097 })
1098 } else if (AI == "Yiyan") {
1099 const node = document.querySelector(".oeNDrlEA")
1100 await node[Object.keys(node)[1]].children[2].props.children[0].props.onChange(text)
1101 await sleep(1e3)
1102 await send(".dialogue_card_item", () => {
1103 document.querySelector("#sendBtn").click()
1104 },1e3)
1105 } else if (AI == "Tongyi") {
1106 setTextareaValue(document.querySelector("textarea.ant-input"))
1107 await send("[class^=questionItem]", () => {
1108 const node2 = document.querySelector(".operateBtn--qMhYIdIu");
1109 node2[Object.keys(node2)[1]].onClick()
1110 })
1111 } else if (AI == "Claude") {
1112 dispatchInput('[contenteditable="true"]')
1113 await sleep(100)
1114
1115 await send('[data-test-render-count]', () => {
1116 document.querySelector("button[aria-label='Send message']").click();
1117
1118 })
1119 } else if (AI == "MyTan") {
1120 dispatchInput(".talk-textarea")
1121 await sleep(100)
1122 await send(".message-container .mytan-model-avatar", () => {
1123 const button = document.querySelector('.send-icon');
1124 button.click()
1125 })
1126 } else if (AI == "ChanlderAi") {
1127 dispatchInput(".chandler-content_input-area")
1128 await sleep(100)
1129 await send(".chandler-ext-content_communication-group", () => {
1130 const button = document.querySelector('.send');
1131 button.click()
1132 })
1133 } else if (AI == "DeepSeek") {
1134 const textarea = document.querySelector("textarea")
1135
1136 setTextareaValue(textarea)
1137 await sleep(100)
1138 await send("._4f9bf79", () => {
1139 const button = document.querySelector('._7436101');
1140 button.click()
1141 })
1142 } else if (AI == "Doubao") {
1143 setTextareaValue(document.querySelector('[data-testid="chat_input_input"]'))
1144 await sleep(100)
1145 await send("[class^=message-block-container]", () => {
1146 const button = document.querySelector("button#flow-end-msg-send");
1147 button.click()
1148 })
1149 } else if (AI == "AIStudio") {
1150 dispatchInput(".text-wrapper textarea")
1151 await sleep(100)
1152 await send("ms-chat-turn", () => {
1153 const button = document.querySelector('ms-run-button button');
1154 button.click()
1155 })
1156 } else if (AI == "Zaiwen") {
1157 dispatchInput('textarea.arco-textarea')
1158 await sleep(100)
1159 await send(".sessions .item", () => {
1160 const button = document.querySelector('img.send');
1161 button.click()
1162 });
1163 } else if (AI == "Yuanbao") {
1164 dispatchInput('.chat-input-editor .ql-editor')
1165 await sleep(100)
1166 await send(".agent-chat__bubble__content", () => {
1167 const button = document.querySelector('.icon-send');
1168 button.click()
1169 })
1170 } else if (AI == "Monica") {
1171 const elements = document.querySelectorAll(".chat--PCM74");
1172
1173 const visibleElements = [];
1174
1175 // 遍历所有元素
1176 elements.forEach(element => {
1177 if (element.style.display !== 'none') {
1178 // 如果不是 'none',将其添加到数组
1179 visibleElements.push(element);
1180 }
1181 });
1182
1183 visibleElements.forEach(element => {
1184 element.parentNode.insertBefore(element, element.parentNode.firstChild);
1185 });
1186
1187 const textarea = document.querySelector('textarea.ant-input')
1188 textarea[Object.keys(textarea)[1]].onChange({ target: { value: text }, currentTarget: { value: text } })
1189 await sleep(100)
1190 await send("[class^=chat-message]", () => {
1191 const button = document.querySelector('[class^=input-msg-btn]')
1192 button[Object.keys(button)[1]].onClick({ isTrusted: true, stopPropagation: () => { } })
1193 })
1194 } else if (AI == "Copilot") {
1195 const node = document.querySelector("textarea#userInput").parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
1196 node[Object.keys(node)[1]].children[1].props.onSubmit(text)
1197 } else if (AI == "Qwen") {
1198 dispatchInput("#chat-input")
1199 await sleep(100)
1200 await send("[id^=message]", () => {
1201 const button = document.querySelector('#send-message-button');
1202 button.click()
1203 })
1204 } else if (AI == "TencentDeepSeek") {
1205 document.querySelector(".question-input").__vue__.onStopStream()
1206 const div = document.querySelector(".question-input-inner__textarea")
1207 await div.__vue__.onChange(text.slice(0,10000))
1208 const chatDiv = document.querySelector(".client-chat");
1209 const total = chatDiv.__vue__.msgList.length
1210 // 等待新回答
1211 while (chatDiv.__vue__.msgList.length - total < 1) {
1212 document.querySelector(".question-input").__vue__.onSendQuestion()
1213 await sleep(100)
1214 }
1215 // 等待新回答
1216 while (chatDiv.__vue__.msgList.length - total < 2) {
1217 await sleep(100)
1218 }
1219 } else if (AI == "AskManyAI") {
1220 dispatchInput('textarea.textarea_input')
1221 await sleep(100)
1222 await send(".main-chat-view .bubble-ai", () => {
1223 const button = document.querySelector('.chat-input-button-inner .fs_button');
1224 button.click()
1225 })
1226 } else if (AI == "Wenxiaobai") {
1227 const textarea = document.querySelector('[class^=MsgInput_input_box] textarea')
1228 await setTextareaValue(textarea)
1229 await sleep(100)
1230 await send("[class^=Answser_answer_content]", async () => {
1231 document.querySelector("[class*=MsgInput_send_btn]").parentNode.click()
1232 })
1233 } else if (AI == "Grok") {
1234 const textarea = document.querySelector('textarea')
1235 const div = document.querySelector("form [contenteditable]")
1236 if (div) {
1237 div.innerHTML = `<p>${text}</p>`
1238 } else {
1239 await setTextareaValue(textarea)
1240 }
1241 await sleep(100)
1242 await send(".items-center .items-start", async () => {
1243 document.querySelector("button[type=submit]")?.click()
1244 })
1245 } else if (AI == "Xiaoyi") {
1246 dispatchInput('textarea')
1247 await sleep(100)
1248 await send(".receive-box", () => {
1249 const button = document.querySelector('.send-button');
1250 button.click()
1251 })
1252 } else if (AI == "Baidu") {
1253 document.querySelector("#chat-input-box").innerText = text
1254 await sleep(100)
1255 await send("[class^=index_answer-container]", () => {
1256 const button = document.querySelector('.send-icon');
1257 button.click()
1258 })
1259 } else if (AI == "Perplexity") {
1260 setEditorText()
1261 await send(".-inset-md", () => {
1262 const node = document.querySelector("button[aria-label=Submit]");
1263 node.click()
1264 })
1265 } else if (AI == "Sider") {
1266 setTextareaValue(document.querySelector("textarea.chatBox-input"))
1267 await sleep(100)
1268 await send(".message-item", () => {
1269 const button = document.querySelector('.send-btn');
1270 button.click()
1271 })
1272 } else if (AI == "Microsoft") {
1273 await setEditorText()
1274 await sleep(1000)
1275 await send("[id^=chatMessageResponser]", () => {
1276 const button = document.querySelector('button[type="submit"]');
1277 button.click()
1278 })
1279 } else if (AI == "GoogleNotebookLM") {
1280 dispatchInput("textarea.query-box-input")
1281 await sleep(1000)
1282 await send("chat-message", () => {
1283 const button = document.querySelector('button[type="submit"]');
1284 button.click()
1285 })
1286 } else if (AI == "MinMax") {
1287 setTextareaValue(document.querySelector("#chat-input"))
1288 await send("section.chat-card-list-wrapper .chat-card-item-wrapper", () => {
1289 const button = document.querySelector('#input-send-icon div');
1290 button.click()
1291 })
1292 } else if (AI == "LMArena") {
1293 setTextareaValue(document.querySelector("form textarea"))
1294 await send("main ol div", () => {
1295 const button = document.querySelector('button[type="submit"]');
1296 button.click()
1297 })
1298 } else if (AI == "GitHubCopilot") {
1299 const textarea = document.querySelector("textarea#copilot-chat-textarea");
1300 textarea[Object.keys(textarea)[0]].pendingProps.onChange({ target: { value: text }, isDefaultPrevented: () => false })
1301 await send(".message-container", () => {
1302 const button = document.querySelector('[class^=ChatInput-module__toolbarButtons] button');
1303 button.click()
1304 })
1305 }
1306
1307 }
1308
1309 // 连续发送
1310 const send = async (selector, callback, delatTime=500) => {
1311 const oldNumber = document.querySelectorAll(selector).length;
1312 callback();
1313 await sleep(delatTime);
1314 while (document.querySelectorAll(selector).length == oldNumber) {
1315 try {
1316 callback();
1317 await sleep(delatTime);
1318 } catch {break}
1319 }
1320 }
1321
1322 const uploadFile = async (base64String, fileName) => {
1323 try {
1324 let fileType;
1325 if (fileName.endsWith("pdf")) {
1326 fileType = "application/pdf";
1327 } else if (fileName.endsWith("png")) {
1328 fileType = "image/png";
1329 } else if (fileName.endsWith("txt") || fileName.endsWith("md")) {
1330 fileType = "text/plain";
1331 }
1332 function base64ToArrayBuffer(base64) {
1333 const binaryString = atob(base64);
1334 const len = binaryString.length;
1335 const bytes = new Uint8Array(len);
1336 for (let i = 0; i < len; i++) {
1337 bytes[i] = binaryString.charCodeAt(i);
1338 }
1339 return bytes.buffer;
1340 }
1341
1342 const AIData = {
1343 ChatGPT: {
1344 method: "input",
1345 selector: "input[type=file]",
1346 },
1347 Tongyi: {
1348 method: "drag",
1349 selector: "[class^=chatInput]",
1350 },
1351 Kimi: {
1352 method: "input",
1353 selector: "input[type=file]"
1354 },
1355 Claude: {
1356 method: "input",
1357 selector: "input[type=file]",
1358 },
1359 AIStudio: {
1360 method: "drag",
1361 selector: ".text-wrapper",
1362 },
1363 Chatglm: {
1364 method: "input",
1365 selector: "input[type=file]",
1366 },
1367 Doubao: {
1368 method: "input",
1369 selector: "input[type=file]",
1370 },
1371 Zaiwen: {
1372 method: "drag",
1373 selector: ".arco-upload-draggable",
1374 },
1375 DeepSeek: {
1376 method: "drag",
1377 selector: ".bf38813a",
1378 },
1379 Yuanbao: {
1380 method: "drag",
1381 selector: ".agent-chat__input-box"
1382 },
1383 ChanlderAi: {
1384 method: "input",
1385 selector: "input[type=file]",
1386 },
1387 Yiyan: {
1388 method: "drag",
1389 selector: ".UxLYHqhv",
1390 },
1391 Poe: {
1392 method: "drag",
1393 selector: ".ChatDragDropTarget_dropTarget__1WrAL"
1394 },
1395 Monica: {
1396 method: "drag",
1397 selector: "[class^=chat-input-v2]"
1398 },
1399 Copilot: {
1400 method: "input",
1401 selector: "input[type=file]",
1402 },
1403 Qwen: {
1404 method: "input",
1405 selector: "input[type=file]",
1406 },
1407 TencentDeepSeek: {
1408 method: "input",
1409 selector: "input[type=file]",
1410 },
1411 AskManyAI: {
1412 method: "input",
1413 selector: "input[type=file]",
1414 },
1415 Wenxiaobai: {
1416 method: "input",
1417 selector: "input[type=file]",
1418 },
1419 Coze: {
1420 method: "input",
1421 selector: "input[type=file]",
1422 },
1423 Baidu: {
1424 method: "drag",
1425 selector: "[class^=chat-bottom-wrapper]"
1426 },
1427 Gemini: {
1428 method: "input",
1429 selector: 'input[type=file]',
1430 },
1431 GoogleNotebookLM: {
1432 method: "input",
1433 selector: 'input[type=file]',
1434 },
1435 Gemini: {
1436 method: "input",
1437 selector: 'input[type=file]',
1438 // 点开元素
1439 before: async () => {
1440 console.log("before")
1441 document.querySelector("uploader .upload-card-button").click();
1442 while (!document.querySelector('[data-test-id="local-image-file-uploader-button"]')) {
1443 console.log("等待弹窗")
1444 await sleep(100)
1445 }
1446
1447 const button = document.querySelector('[data-test-id="local-image-file-uploader-button"]')
1448 setTimeout(() => {
1449 console.log("延迟点击", button);
1450 button.click();
1451 }, 100);
1452
1453 },
1454 after: async ()=>{
1455 console.log("after")
1456 document.querySelector("uploader .upload-card-button").click();
1457 await sleep(1000)
1458 while (document.querySelector('uploader-file-preview-container [role="progressbar"]') ) {
1459 console.log("wait...")
1460 await sleep(1000)
1461 }
1462 }
1463 }
1464 };
1465 if (!AIData[AI]) {
1466 AIData[AI] = {
1467 method: "input",
1468 selector: 'input[type=file]',
1469 }
1470 }
1471 if (AIData[AI].beforeCallback) {
1472 AIData[AI].beforeCallback()
1473 await sleep(3e3)
1474 }
1475
1476 if (AIData[AI]) {
1477 const { method, selector, until, before, after } = AIData[AI];
1478 if (method === "input") {
1479 // 创建一个虚拟的文件对象
1480 const fileContent = base64ToArrayBuffer(base64String);
1481 const file = new File([fileContent], fileName, { type: fileType });
1482
1483 // 创建一个DataTransfer对象,并添加文件
1484 const dataTransfer = new DataTransfer();
1485 dataTransfer.items.add(file);
1486 if (before) {
1487 await before()
1488 while (!document.querySelector(selector)) {
1489 console.log("等待", selector)
1490 await sleep(1e3)
1491 }
1492 }
1493 let fileInput
1494 let fileInputs = document.querySelectorAll(selector);
1495 if (fileInputs.length == 1) {
1496 fileInput = fileInputs[0]
1497 } else {
1498 fileInput = [...fileInputs].find(i => i.accept.includes(fileType) || i.multiple)
1499 }
1500 if (fileInput) {
1501 fileInput.files = dataTransfer.files;
1502 fileInput.dispatchEvent(new Event('change', { bubbles: true }));
1503 } else {
1504 window.alert(AI + "上传失败,请刷新网页后重试。若多次重试仍无法上传,请检查更新脚本,更新后仍如此,请联系开发者修复。")
1505 }
1506 if (after) {
1507 await after()
1508 }
1509 } else if (method === "drag") {
1510 // 创建一个虚拟的文件对象
1511 const fileContent = base64ToArrayBuffer(base64String);
1512 const file = new File([fileContent], fileName, { type: fileType });
1513
1514 // 创建一个DataTransfer对象,并添加文件
1515 const dataTransfer = new DataTransfer();
1516 dataTransfer.items.add(file);
1517
1518 // 查找可拖放的区域或上传区域
1519 const dropZone = document.querySelector(selector); // 使用提供的选择器查找拖放区域
1520 if (!dropZone) {
1521 window.alert(AI + "未获取到dropZone,请联系开发者修复")
1522 }
1523 // 创建dragenter, dragover, drop事件
1524 const dragStartEvent = new DragEvent("dragstart", {
1525 bubbles: true,
1526 dataTransfer: dataTransfer,
1527 cancelable: true
1528 });
1529 const dropEvent = new DragEvent("drop", {
1530 bubbles: true,
1531 dataTransfer: dataTransfer,
1532 cancelable: true
1533 });
1534
1535 // 依次派发事件,模拟拖放过程
1536 dropZone.dispatchEvent(dragStartEvent);
1537 dropZone.dispatchEvent(dropEvent);
1538 }
1539 if (until) {
1540 await sleep(100)
1541 while (!until()) {
1542 await sleep(100)
1543 }
1544 }
1545 }
1546 } catch (e) {
1547 console.error(e);
1548 }
1549 };
1550
1551
1552 // 阻塞
1553 function sleep(ms) {
1554 execInZotero()
1555 return new Promise(resolve => setTimeout(resolve, ms));
1556 }
1557
1558 // 支持:多个联动页面打开
1559 const LOCK_KEY = 'gpt_connector_running';
1560 const TAB_ID = Math.random().toString(36).substr(2, 9); // Unique ID for each tab
1561
1562 GM_registerMenuCommand('⭐️ 优先', () => {
1563 isRunning = true
1564 releaseLock()
1565 acquireLock()
1566 window.alert("⭐️ 优先")
1567 });
1568
1569 GM_registerMenuCommand('🔗 运行', () => {
1570 isRunning = true
1571 window.alert("🔗 已运行")
1572 });
1573
1574 GM_registerMenuCommand('🎊 断开', () => {
1575 isRunning = false
1576 releaseLock()
1577 window.alert("🎊 断开")
1578 });
1579
1580
1581 function acquireLock() {
1582 let lockInfo = JSON.parse(GM_getValue(LOCK_KEY, "{}"));
1583
1584 if (lockInfo && lockInfo.isLocked) {
1585 if (lockInfo.tabId === TAB_ID) {
1586 // The current tab already holds the lock
1587 // console.log('This tab already holds the lock:', TAB_ID);
1588 return true;
1589 } else {
1590 // Lock is held by another tab
1591 // console.log('Another tab is already running the script. Exiting...');
1592 return false;
1593 }
1594 } else {
1595 // Lock is not set, acquire it for this tab
1596 GM_setValue(LOCK_KEY, JSON.stringify({ isLocked: true, tabId: TAB_ID }));
1597 // console.log('Lock acquired by tab:', TAB_ID);
1598 return true;
1599 }
1600 }
1601
1602 function releaseLock() {
1603 GM_setValue(LOCK_KEY, JSON.stringify({ isLocked: false, tabId: null }));
1604 }
1605
1606 // Add an event listener to release the lock when the page is unloaded
1607 window.addEventListener('beforeunload', releaseLock);
1608 window.addEventListener('reload', releaseLock);
1609
1610 releaseLock()
1611 // 通信
1612 while (true) {
1613 if (!acquireLock()) {
1614 await sleep(1000)
1615 continue;
1616 }
1617 if (!isRunning) {
1618 await execInZotero(`
1619 window.Meet.Connector.time = 0;
1620 `)
1621 await sleep(1000)
1622 continue;
1623 }
1624 try {
1625 const tasks = (await execInZotero(`
1626 window.Meet.Connector
1627 `)).tasks
1628
1629 if (!tasks || tasks.length == 0) {
1630 await sleep(500)
1631 continue
1632 }
1633 const task = tasks.slice(-1)[0]
1634 if (task.type == "pending") {
1635 if (task.file || task.files && task.responseText == undefined) {
1636 await execInZotero(`
1637 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
1638 task.type = "done"
1639 `)
1640 if (task.file) {
1641 await uploadFile(task.file.base64String, task.file.name)
1642 } else if (task.files){
1643 for (let file of task.files) {
1644 await uploadFile(file.base64String, file.name)
1645 }
1646 }
1647 } else if (task.requestText) {
1648 await setText(task.requestText)
1649 // 操作浏览器提问
1650 await execInZotero(`
1651 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
1652 task.requestText = "";
1653 task.responseText = "<p>Waiting ${AI}...</p>";
1654 `)
1655 } else {
1656 let isDone = false, text = "", type = "html"
1657 const setZoteroText = async () => {
1658 if (typeof (text) !== "string") { return }
1659 await execInZotero(`
1660 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
1661 task.responseText = ${JSON.stringify(text)};
1662 task.type = ${isDone} ? "done" : "pending";
1663 task.responseType = "${type}"
1664 `)
1665 if (isDone) {
1666 await sleep(1000)
1667 await execInZotero(`
1668 let task = window.Meet.Connector.tasks[window.Meet.Connector.tasks.length-1]
1669 task.responseText = ${JSON.stringify(text)};
1670 `)
1671 }
1672 }
1673 if (AI == "Poe") {
1674 type = "markdown"
1675 const lastNode = [...document.querySelectorAll("[class^=ChatMessage_chatMessage] [class^=Message_selectableText]")].slice(-1)[0]
1676 console.log("lastNode[Object.keys(lastNode)[1]]", lastNode[Object.keys(lastNode)[1]])
1677 const props = lastNode[Object.keys(lastNode)[0]].alternate.child.memoizedProps
1678 text = props.text
1679 isDone = Boolean(lastNode.closest("[class^=ChatMessagesView_messageTuple]").querySelector("[class^=ChatMessageActionBar_actionBar]"))
1680 await setZoteroText()
1681
1682 } else if (AI == "Copilot") {
1683 const lastAnwser = [...document.querySelectorAll('[data-content=ai-message]')].slice(-1)[0]
1684 type = "markdown"
1685 const props = lastAnwser[Object.keys(lastAnwser)[0]].pendingProps.children[1][0].props
1686 text = props.item.text
1687 isDone = props.isStreamingComplete
1688 await setZoteroText()
1689 } else if (AI == "TencentDeepSeek") {
1690 const div = document.querySelector(".client-chat");
1691 const msg = div.__vue__.msgList.slice(-1)[0]
1692 isDone = msg.is_final
1693 const content = div.__vue__.msgList.slice(-1)[0].content
1694 if (!content) {
1695 text = "> " + div.__vue__.msgList.slice(-1)[0].agent_thought.procedures[0].debugging.content.trim().replace(/\n+/g, "\n")
1696 } else {
1697 text = content
1698 }
1699 type = "markdown"
1700 await setZoteroText()
1701 } else if (AI == "Xiaoyi") {
1702 const div = [...document.querySelectorAll(".receive-box")].slice(-1)[0];
1703 isDone = Boolean(div.closest(".msg-content") && div.closest(".msg-content").querySelector(".tool-bar"))
1704 text = div.querySelector(".answer-cont").innerHTML
1705 type = "html"
1706
1707 await setZoteroText()
1708 } else if (AI == "Microsoft") {
1709 const div = document.querySelector('[id^=chatMessageResponser]')
1710 if (!div) { return }
1711 text = div[Object.keys(div)[1]].children[0].props.text
1712 isDone = div.closest('[role="article"]').querySelector(".fai-CopilotMessage__footnote")
1713 type = "markdown"
1714
1715 await setZoteroText()
1716
1717 }
1718 }
1719 }
1720 } catch (e) {
1721 if (String(e).includes("Network error")) {
1722 await sleep(1e3)
1723 } else {
1724 console.log(e)
1725 }
1726 }
1727 await sleep(100)
1728 }
1729})();