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 += `![](${data.replace.multimedias[0].url})\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})();