ChatGPT Vô cùng ∞

Tạo ra vô số câu trả lời từ ChatGPT thông minh (bằng bất kỳ ngôn ngữ nào!)

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyforks.org/scripts/537701/1598244/Chat%20GPT%20%28%20Nguy%E1%BB%85n%20V%C5%A9%20Nam%29.js

// ==UserScript==
// @name                Chat GPT ( Nguyễn Vũ Nam)
// @name:af             ChatGPT Oneindig ∞
// @name:ar             دردشةGPT إنفينيتي ∞
// @name:az             ChatGPT Sonsuzluq ∞
// @name:be             ChatGPT Бясконцасць ∞
// @name:bg             ChatGPT Безкрайност ∞
// @name:bn             ChatGPT ইনফিনিটি ∞
// @name:bo             ChatGPT དག་སྐྱེས་ཡོད་པ་ ∞
// @name:bs             ChatGPT Beskrajnost ∞
// @name:ca             ChatGPT Infinit ∞
// @name:ckb            ChatGPT نەپێندییە ∞
// @name:cs             ChatGPT Nekonečno ∞
// @name:cy             ChatGPT Anfeidredd ∞
// @name:da             ChatGPT Uendelighed ∞
// @name:de             ChatGPT Unendlichkeit ∞
// @name:dv             ChatGPT ނުވަތަ ބާވައްޖޭގެން ∞
// @name:dz             ChatGPT རྩེད་སྒྲིབ་གཉིས་ ∞
// @name:el             ChatGPT Άπειρο ∞
// @name:eo             ChatGPT Infinito ∞
// @name:es             ChatGPT Infinito ∞
// @name:et             ChatGPT Lõpmatus ∞
// @name:eu             ChatGPT Infinitua ∞
// @name:fa             ChatGPT بینهایت ∞
// @name:fi             ChatGPT Ääretön ∞
// @name:fo             ChatGPT Óendanlighed ∞
// @name:fr             ChatGPT Infini ∞
// @name:fr-CA          ChatGPT Infini ∞
// @name:gl             ChatGPT Infinito ∞
// @name:gu             ChatGPT અનંત ∞
// @name:haw            ChatGPT Māhina ʻole ∞
// @name:he             ChatGPT אינסוף ∞
// @name:hi             ChatGPT अनंत ∞
// @name:hr             ChatGPT Beskrajnost ∞
// @name:hu             ChatGPT Végtelenség ∞
// @name:hy             ChatGPT Անվերջ ∞
// @name:id             ChatGPT Infinity ∞
// @name:is             ChatGPT Óendanleiki ∞
// @name:it             ChatGPT Infinito ∞
// @name:ja             ChatGPT 無限 ∞
// @name:ka             ChatGPT უსასრულობა ∞
// @name:km             ChatGPT អនឡាញ ∞
// @name:kn             ChatGPT ಅನಂತ ∞
// @name:ko             ChatGPT 무한 ∞
// @name:ku             ChatGPT Pêşangeh ∞
// @name:ky             ChatGPT Ички ∞
// @name:la             ChatGPT Infinitas ∞
// @name:lb             ChatGPT Unendlechkeet ∞
// @name:lo             ChatGPT ບໍ່ໄປສູ່ຈັກຂອງສາມຫວ່າງ ∞
// @name:lt             ChatGPT Begalybė ∞
// @name:lv             ChatGPT Bezgalība ∞
// @name:mk             ChatGPT Бесконечност ∞
// @name:ml             ChatGPT അനന്തത ∞
// @name:mn             ChatGPT Тэгш бус ∞
// @name:mt             ChatGPT Infinità ∞
// @name:my             ChatGPT မြင်ကွင်း ∞
// @name:ne             ChatGPT अनंत ∞
// @name:nl             ChatGPT Oneindigheid ∞
// @name:no             ChatGPT Uendelighet ∞
// @name:pa             ChatGPT ਇਨਫਿਨਿਟੀ ∞
// @name:pl             ChatGPT Nieskończoność ∞
// @name:ps             ChatGPT لامکان ∞
// @name:pt             ChatGPT Infinito ∞
// @name:pt-BR          ChatGPT Infinito ∞
// @name:ro             ChatGPT Infinitate ∞
// @name:ru             ChatGPT Бесконечность ∞
// @name:si             ChatGPT අනන්තය ∞
// @name:sk             ChatGPT Nekonečno ∞
// @name:sl             ChatGPT Neskončnost ∞
// @name:so             ChatGPT Qaybtiisa ∞
// @name:sr             ChatGPT Бескрајност ∞
// @name:sv             ChatGPT Oändlighet ∞
// @name:ta             ChatGPT முடிவிலிருந்து ∞
// @name:te             ChatGPT అనంతత ∞
// @name:tg             ChatGPT Беоҳӣ ∞
// @name:th             ChatGPT อนันต์ ∞
// @name:ti             ChatGPT መደመር ∞
// @name:tk             ChatGPT Sonsuzluk ∞
// @name:tr             ChatGPT Sonsuzluk ∞
// @name:uk             ChatGPT Нескінченність ∞
// @name:ur             ChatGPT بے انتہا ∞
// @name:vi             ChatGPT Vô cùng ∞
// @name:yi             ChatGPT אינפיניטי ∞
// @name:zh             ChatGPT 无限 ∞【支持免梯镜像版】
// @name:zh-CN          ChatGPT 无限 ∞【支持免梯镜像版】
// @name:zh-HK          ChatGPT 無限 ∞【支持免梯镜像版】
// @name:zh-SG          ChatGPT 无限 ∞【支持免梯镜像版】
// @name:zh-TW          ChatGPT 無限 ∞【支持免梯镜像版】
// @description         Generate endless answers from all-knowing ChatGPT (in any language!)
// @description:af      Genereer eindelose antwoorde van die alwetende ChatGPT (in enige taal!)
// @description:am      ሓበሬታይ ኣይነበርና መልእኽቲ ኣለኹም ኣሎኻዊ ያልኣይት ChatGPT (በቋንቋ!)
// @description:ar      قم بتوليد إجابات لا نهائية من ChatGPT المعرف بكل شيء (بأي لغة!)
// @description:az      Hər şeyi bilən ChatGPT-dən sonsuz cavablar yaradın (hər hansı bir dil ilə!)
// @description:be      Стварыце бясконцы адказы ад ўсеведучага ChatGPT (на любой мове!)
// @description:bem     Pamene mwaposa mafolosho a mʌnoze ChatGPT (mu lupiya lwa ndalama!)
// @description:bg      Генерирайте безкрайни отговори от всезнайния ChatGPT (на всяк език!)
// @description:bn      সর্বজ্ঞ চ্যাটজিপিটি থেকে অসীম উত্তর তৈরি করুন (যে কোন ভাষায়!)
// @description:bo      བཟོས་པ་བརྗོད་པའི་ChatGPTགི་གྲོས་པར་འགྲུབ་འདེམས་བསྐྱེད་མི་འོངས་པ། (ལས་སྐབས་ཀྱི་སྐད་ཡིག་གི་སྐད་!)
// @description:bs      Generišite beskonačne odgovore od sveznajućeg ChatGPT (na bilo kom jeziku!)
// @description:ca      Genereu respostes infinites des de l'omniscient ChatGPT (en qualsevol idioma!)
// @description:ceb     Sukdagon ang walay katapusan nga mga tubag gikan sa tanan-mahibaw-anong ChatGPT (sa bisan unsang pinulongan!)
// @description:ckb     پێشبینی چاتگپتەکە دیارییەکانی پێکراوەکان بنووسە (بە هەر زمانێکی دیکە!)
// @description:cs      Generujte nekonečné odpovědi od vševědoucího ChatGPT (v jakémkoli jazyce!)
// @description:cy      Cynhyrchwch atebion diddiwedd o ChatGPT y gŵr sy'n gwybod popeth (mewn unrhyw iaith!)
// @description:da      Generer endeløse svar fra den altvidende ChatGPT (på hvilket som helst sprog!)
// @description:de      Generieren Sie endlose Antworten von dem allwissenden ChatGPT (in beliebiger Sprache!)
// @description:dv      އެހެނދުކަޗެއްވެސީ ޗެއްސިސްޓޯނަށް ނިޔަލައިފައިވަނީ ސުރުކޮށް ފީހުން (ކަންއެވެސް ހަމަވެސް!)
// @description:dz      ཆགས་ཀྱིས་ལ་བརྟེན་གང་རུང་གི་ChatGPTགི་ཚོགས་པའི་སྐད་ཡིག་གྱི་སྐད་ཡིག་བྱེད་པ། (བླ་མའི་སྐད་ཡིག་!)
// @description:el      Δημιουργήστε ατελείωτες απαντήσεις από το γνώστη ChatGPT (σε οποιαδήποτε γλώσσα!)
// @description:eo      Kreu nesfinaĵajn respondojn el la ĉio-scianta ChatGPT (en ajn lingvo!)
// @description:es      Genera respuestas infinitas desde el ChatGPT omnisciente (¡en cualquier idioma!)
// @description:et      Loo lõputuid vastuseid kõike-teadvast ChatGPT-st (mis tahes keeles!)
// @description:eu      Sortu erantzun infinituak guztiz-arduratsuaren ChatGPT-tik (edozein hizkuntzan!)
// @description:fa      پاسخهای بیپایانی را از ChatGPT همهدانا تولید کنید (به هر زبانی!)
// @description:fi      Luo loputtomia vastauksia kaikkitietävästä ChatGPT:stä (millä tahansa kielellä!)
// @description:fo      Skapa endalaus svør frá altvitandi ChatGPT (á hvørjum málrøð!)
// @description:fr      Générez des réponses infinies à partir de ChatGPT qui sait tout (dans n'importe quelle langue!)
// @description:fr-CA   Générez des réponses infinies à partir de ChatGPT qui sait tout (dans n'importe quelle langue!)
// @description:gd      Cruthaich freagairtean gun crìoch à ChatGPT a tha a' faighneachd gach càil (ann an sam bith cànan!)
// @description:gl      Xere as respostas infinitas do ChatGPT que todo o sabe (en calquera lingua!)
// @description:gu      જ્ઞાનવાન ChatGPT થી અનંત જવાબો ઉત્પન કરો (કોઈ પણ ભાષામાં!)
// @description:haw     Haleleʻa i nā ʻōlelo pālulelo mai ke kupa ʻike loa o ChatGPT (i loko o kēlā ʻōlelo aku aʻe!)
// @description:he      יצרו תשובות אינסופיות מה-ChatGPT המבין הכל (בכל שפה!)
// @description:hi      सभी-जाननेवाले चैटजीपीटी से अनंत उत्तरों का उत्पादन करें (किसी भी भाषा में!)
// @description:hr      Generirajte beskrajne odgovore iz sveznajućeg ChatGPT-a (na bilo kojem jeziku!)
// @description:ht      Jenere repons san fen soti nan ChatGPT k-ap konnen tout bagay yo (nan nenpòt lang!)
// @description:hu      Végtelen válaszokat generáljon a mindentudó ChatGPT-ből (bármely nyelven!)
// @description:hy      Ստեղծեք անսահմանափակ պատասխաններ ամենագիտական ChatGPT-ից (ցանկացած լեզուն մեջ!)
// @description:id      Hasilkan jawaban tak terbatas dari ChatGPT yang tahu segalanya (dalam bahasa apa pun!)
// @description:is      Búið til endalaus svör frá allvissu ChatGPT (á hvaða tungumáli sem er!)
// @description:it      Genera risposte infinite dall'onnisciente ChatGPT (in qualsiasi lingua!)
// @description:ja      あらゆる言語で全知のChatGPTから無限の回答を生成します!
// @description:jv      Mbukak panggung jawaban saka ChatGPT kang suwénéh kabèh (kénging basa apapun!)
// @description:ka      შექმენით უსასრულო პასუხები ყველგანაწილებული ChatGPT-დან (ნელა ენაზე!)
// @description:kab     Kkes-as yidir seg yisem-asen n tafriqt ara ddunnit ChatGPT (di tifrat ara ddunit!)
// @description:kk      Барлық тілде бар білген ChatGPT-ден шексіз жауаптар жасау (қандай да тілде!)
// @description:km      បង្កើតចម្លើយមិនឃើញចប់ពី ChatGPT ដែលចែកសង្ស័យទាំងអស់ (ជាន់គ្រប់ភាសា!)
// @description:kn      ಎಲ್ಲರು ತಿಳಿದಿರುವ ಚಾಟ್ಜಿಪಿಟಿಯಿಂದ ಅನಂತ ಉತ್ತರಗಳನ್ನು ರಚಿಸಿ (ಯಾವುದೇ ಭಾಷೆಯಲ್ಲಿ!)
// @description:ko      어떤 언어로든지 무궁무진한 답변을 만들어내는 ChatGPT입니다!
// @description:ku      Ji ChatGPT-ê, ku hemî tiştan dizane, bersiva li serbazên li dor qewî yên hilbijartin (bi hertiştî zimanek!)
// @description:ky      Түз билимдүү ChatGPT боюнча чыгармаларды жаса
// @description:la      Genera infinitas responsiones de ChatGPT omniscio (in qualibet lingua!)
// @description:lb      Generéiert endlos Äntwerten vum allwëssende ChatGPT (op jiddere Sprooch!)
// @description:lo      ສ້າງຜົນສົດທີ່ສາມາດເລືອກຈາກພາສາທີ່ຮູ້ບຸກກຳລັງ ChatGPT (ໃນພາສາໃດໜຶ່ງ!)
// @description:lt      Sugeneruokite begalinį atsakymų kiekį iš visai žinoančio ChatGPT (bet kuria kalba!)
// @description:lv      Ģenerē nebeidzamus atbilžu variantus no vissapratīgā ChatGPT (jebkurā valodā!)
// @description:mg      Mandoza ny valiny miaraka amin'ny ChatGPT mahalala ny zavatra rehetra (amin'ny fiteny iray kokoa!)
// @description:mi      Whakapūmau i ngā whakautu kore mutunga mai i te ChatGPT matau i ngā reo katoa!
// @description:mk      Генерирајте безброј одговори од сèзнаената ChatGPT (на било кој јазик!)
// @description:ml      എന്നെഴുതാൻ അനന്യനായ ChatGPT (ഏതെങ്കിലും ഭാഷയിൽ) ഇന്നേക്കും വേണ്ടി അവസാനിപ്പിക്കുക!
// @description:mn      Сүүлд үлдсэнээсээ хайрцаг болох ChatGPT (ямар ч хэл дээр) дээрхээр ярих
// @description:ms      Cipta pelbagai jawapan daripada ChatGPT yang tahu segala-galanya (dalam apa-apa bahasa!)
// @description:mt      Iġġenera risposti infiniti mill-ChatGPT li kollha jaf (f'xi lingwa!)
// @description:my      အားလုံးကိုသင်ရိုးရိုးဆွဲနိုင်သည့် ChatGPT (တစ်ခုမှာယူသုံးရန်!)
// @description:ne      सबै-ज्ञानी ChatGPT बाट अनंत उत्तरहरू उत्पन्न गर्नुहोस् (कुनै पनि भाषामा!)
// @description:nl      Genereer eindeloze antwoorden van alwetende ChatGPT (in elke taal!)
// @description:no      Generer endeløse svar fra allvitende ChatGPT (på hvilket som helst språk!)
// @description:ny      Galimoto mabwino a mtendere zosiyanasiyana kuchokera ku ChatGPT woyankhula zonse! (muyankhulitsa chilichonse!)
// @description:pa      ਸਭ ਜਾਣ ਵਾਲੇ ChatGPT ਤੋਂ ਲੰਬੇ ਉੱਤਰਾਂ ਨੂੰ ਬਣਾਓ (ਕਿਸੇ ਵੀ ਭਾਸ਼ਾ ਵਿੱਚ!)
// @description:pap     Hasi respuesta sinfin di ChatGPT ku tur sabi (na kua idioma!)
// @description:pl      Generuj nieskończone odpowiedzi od wszechwiedzącego ChatGPT (w dowolnym języku!)
// @description:ps      د هغوی چټکال ChatGPT لخوا نهایت جوابونه جوړه کړئ (د هر یوه ژبه کی!)
// @description:pt      Gere respostas infinitas do ChatGPT onisciente (em qualquer idioma!)
// @description:pt-BR   Gere respostas infinitas do ChatGPT onisciente (em qualquer idioma!)
// @description:rn      Basha inzira nk'itandukanye z'uko ChatGPT uzi (mu rurimi rwose!)
// @description:ro      Generează răspunsuri infinite de la ChatGPT omniscient (în orice limbă!)
// @description:ru      Генерируйте бесконечные ответы от всезнающего ChatGPT (на любом языке!)
// @description:rw      Banda inama z'imirimo isigaye zose z'umuco wa ChatGPT (mu rurimi rwose!)
// @description:sg      Tia ngamana nga mbetela ti ChatGPT tozuwa mitayi mingi (po wundi ndimi yo ngambo!)
// @description:si      සියළු දැනටමත් සහතිකපත්කරන තවත් සිතියම් සැකසුම් ලබා දෙන්නාවූ ChatGPT සෑදීම (ඕනෑම භාෂාවෙන්ම!)
// @description:sk      Generujte nekonečné odpovede od vševietajúceho ChatGPT (v akomkoľvek jazyku!)
// @description:sl      Generirajte neskončne odgovore iz vsevednega ChatGPT (v katerem koli jeziku!)
// @description:sm      Faia se faiga tele i le ChatGPT eseese (i nisi gagana!)
// @description:sn      Simudza mhinduro dzakawanda kubva kune ChatGPT ichiwanikwa chisarudzo! (mune rimwe nguva!)
// @description:so      Kor u qaado jawaabo kala duwan oo ka soo saar ChatGPT (luuqad ka mid ah!)
// @description:sr      Генеришите безброј одговора од свемоћног ChatGPT (на било ком језику!)
// @description:sv      Generera oändliga svar från allvetande ChatGPT (på valfritt språk!)
// @description:sw      Toa majibu yasiyokuwa na mwisho kutoka kwa ChatGPT mwenye maarifa yote (katika lugha yoyote!)
// @description:ta      அனைத்து தகவலையும் அறியப்பட்ட சொந்தமான ChatGPT இலிருந்து முடிவுகளை உருவாக்கவும் (எந்த மொழியிலும்!)
// @description:te      అన్నింటినీకింట మాటలు రాయండి ChatGPT గురించి అనేక జవాబాలను సృష్టించండి (ఏదైనా భాషలో!)
// @description:tg      Ҷавобҳои бесуданни ChatGPTи ҳамаифақат (дар як забони муайяни!)
// @description:th      สร้างคำตอบไม่สิ้นสุดจาก ChatGPT ที่รู้ทุกสิ่ง (ในภาษาใดก็ได้!)
// @description:ti      ክርስትን ለማውጣት ከገጽታውን ChatGPT የተከሳሽ አስተካክል አስተካክሎት (በእውነት ቋንቋ!)
// @description:tk      Şeýle anlaşylýan ChatGPT (her hili üçin) arasyndan bäşmüňhat jawaplar döret
// @description:tn      Hlela zitsha zwine zwiṱhiselela kha ChatGPT vhufudziṱe (muvhili wo vha u ite!)
// @description:to      Fakatonutonu ʻihe ngāuekesi mei he ChatGPT fakaongoongo toenga (i he lea faka-Tonga ia!)
// @description:tpi     Sanapim hamamasples long save olsem ChatGPT i masples (long wanpela tok ples!)
// @description:tr      Bilge ChatGPT'den sonsuz cevaplar üret (herhangi bir dilde!)
// @description:uk      Генеруйте безкінечні відповіді від усезнаючого ChatGPT (на будь-якій мові!)
// @description:ur      سب جاننے والے ChatGPT سے لامتناہی جوابات پیدا کریں (کسی بھی زبان میں!)
// @description:uz      Barcha biladigan ChatGPT dan cheklovli javoblar oling (hech qanday tilda ham)!
// @description:vi      Tạo ra vô số câu trả lời từ ChatGPT thông minh (bằng bất kỳ ngôn ngữ nào!)
// @description:xh      Bhala izinto ezingekwaziyo eziyimfihlo kusuka ku ChatGPT (ngolwimi lwanyanetha!)
// @description:yi      דזשענערייט סאָף ענטפֿערס פֿון אַלע-געוויסן ChatGPT (אין קיין שפּראַך!)
// @description:zh      只添加了免梯镜像网站(login-gpt.com & webchat-ai.com)的支持,未做其他改动
// @description:zh-CN   只添加了免梯镜像网站(login-gpt.com & webchat-ai.com)的支持,未做其他改动
// @description:zh-HK   只添加了免梯镜像网站(login-gpt.com & webchat-ai.com)的支持,未做其他改动
// @description:zh-SG   只添加了免梯镜像网站(login-gpt.com & webchat-ai.com)的支持,未做其他改动
// @description:zh-TW   只添加了免梯镜像网站(login-gpt.com & webchat-ai.com)的支持,未做其他改动
// @author              Adam Lui
// @namespace           https://github.com/adamlui
// @version             2024.8.29.1
// @license             MIT
// @match               *://chatgpt.com/*
// @match               *://chat.openai.com/*
// @match               *://login-gpt.com/*
// @match               *://www.login-gpt.com/*
// @match               *://webchat-ai.com/*
// @match               *://www.webchat-ai.com/*
// @icon                https://media.chatgptinfinity.com/images/icons/infinity-symbol/black/icon48.png?fa77d27
// @icon64              https://media.chatgptinfinity.com/images/icons/infinity-symbol/black/icon64.png?fa77d27
// @compatible          chrome
// @compatible          firefox
// @compatible          edge
// @compatible          opera
// @compatible          brave
// @compatible          vivaldi
// @compatible          waterfox
// @compatible          librewolf
// @compatible          ghost
// @compatible          qq
// @compatible          whale
// @compatible          kiwi
// @require             https://cdn.jsdelivr.net/npm/@kudoai/[email protected]/dist/chatgpt.min.js#sha256-GYFysy53+YoEcZjRVc9GQtL4VlmCpPpgXwjVgVm9rMk=
// @connect             cdn.jsdelivr.net
// @connect             greasyforks.org
// @grant               GM_setValue
// @grant               GM_getValue
// @grant               GM_registerMenuCommand
// @grant               GM_unregisterMenuCommand
// @grant               GM_xmlhttpRequest
// @grant               GM.xmlHttpRequest
// @noframes
// @homepageURL         https://www.chatgptinfinity.com
// @supportURL          https://support.chatgptinfinity.com
// @contributionURL     https://github.com/sponsors/adamlui
// ==/UserScript==
 
// Documentation: https://docs.chatgptinfinity.com
// NOTE: This script relies on the powerful chatgpt.js library @ https://chatgpt.js.org © 2023–2024 KudoAI & contributors under the MIT license.
 
(async () => {
 
    // Init CONFIG
    const config = {
        appName: 'ChatGPT Infinity', appSymbol: '∞', keyPrefix: 'chatGPTinfinity',
        gitHubURL: 'https://github.com/adamlui/chatgpt-infinity',
        greasyForkURL: 'https://greasyforks.org/scripts/465051-chatgpt-infinity',
        mediaHostURL: 'https://media.chatgptinfinity.com/',
        latestAssetCommitHash: '7bbe0b5' } // for cached messages.json + navicon
    config.updateURL = config.greasyForkURL.replace('https://', 'https://update.')
        .replace(/(\d+)-?([a-zA-Z-]*)$/, (_, id, name) => `${ id }/${ !name ? 'script' : name }.meta.js`)
    config.supportURL = 'https://support.chatgptinfinity.com'
    config.assetHostURL = config.gitHubURL.replace('github.com', 'cdn.jsdelivr.net/gh') + `@${config.latestAssetCommitHash}/`
    config.userLanguage = chatgpt.getUserLanguage()
    loadSetting('autoScrollDisabled', 'autoStart', 'replyInterval', 'replyLanguage', 'replyTopic', 'toggleHidden')
    if (!config.replyLanguage) saveSetting('replyLanguage', config.userLanguage) // init reply language if unset
    if (!config.replyTopic) saveSetting('replyTopic', 'ALL') // init reply topic if unset
    if (!config.replyInterval) saveSetting('replyInterval', 7) // init refresh interval to 7 secs if unset
 
    // Init FETCHER
    const xhr = getUserscriptManager() == 'OrangeMonkey' ? GM_xmlhttpRequest : GM.xmlHttpRequest
 
    // Init MESSAGES
    let msgs = {}
    if (!config.userLanguage.startsWith('en')) msgs = await new Promise(resolve => {
        const msgHostDir = config.assetHostURL + 'greasemonkey/_locales/',
              msgLocaleDir = ( config.userLanguage ? config.userLanguage.replace('-', '_') : 'en' ) + '/'
        let msgHref = msgHostDir + msgLocaleDir + 'messages.json', msgXHRtries = 0
        xhr({ method: 'GET', url: msgHref, onload: onLoad })
        function onLoad(resp) {
            try { // to return localized messages.json
                const msgs = JSON.parse(resp.responseText), flatMsgs = {}
                for (const key in msgs)  // remove need to ref nested keys
                    if (typeof msgs[key] == 'object' && 'message' in msgs[key])
                        flatMsgs[key] = msgs[key].message
                resolve(flatMsgs)
            } catch (err) { // if bad response
                msgXHRtries++ ; if (msgXHRtries == 3) return resolve({}) // try up to 3X (original/region-stripped/EN) only
                msgHref = config.userLanguage.includes('-') && msgXHRtries == 1 ? // if regional lang on 1st try...
                    msgHref.replace(/([^_]+_[^_]+)_[^/]*(\/.*)/, '$1$2') // ...strip region before retrying
                        : ( msgHostDir + 'en/messages.json' ) // else use default English messages
                xhr({ method: 'GET', url: msgHref, onload: onLoad })
            }
        }
    })
 
    // Init MENU objs
    const menuIDs = [] // to store registered cmds for removal while preserving order
    const menuState = {
        symbol: ['❌', '✔️'], word: ['OFF', 'ON'],
        separator: getUserscriptManager() == 'Tampermonkey' ? ' — ' : ': '
    }
 
    // Create browser TOOLBAR MENU or DISABLE SCRIPT if extension installed
    const extensionInstalled = await Promise.race([
        new Promise(resolve => {
            (function checkExtensionInstalled() {
                if (document.querySelector('[cif-extension-installed]')) resolve(true)
                else setTimeout(checkExtensionInstalled, 200)
            })()
        }), new Promise(resolve => setTimeout(() => resolve(false), 1500))])
    if (extensionInstalled) { // disable script/menu
        GM_registerMenuCommand(menuState.symbol[0] + ' ' + ( msgs.menuLabel_disabled || 'Disabled (extension installed)' ),
            () => { return }) // disable menu
        return // exit script
    } else registerMenu() // create functional menu
 
    // Define SCRIPT functions
 
    function loadSetting(...keys) { keys.forEach(key => { config[key] = GM_getValue(config.keyPrefix + '_' + key, false) })}
    function saveSetting(key, value) { GM_setValue(config.keyPrefix + '_' + key, value) ; config[key] = value }
    function safeWindowOpen(url) { window.open(url, '_blank', 'noopener') } // to prevent backdoor vulnerabilities
    function getUserscriptManager() { try { return GM_info.scriptHandler } catch(err) { return 'other' }}
 
    // Define MENU functions
 
    function registerMenu() {
 
        // Add command to toggle Infinity Mode
        const imLabel = menuState.symbol[+config.infinityMode] + ' '
                      + ( msgs.menuLabel_infinityMode || 'Infinity Mode' ) + ' ∞ '
                      + menuState.separator + menuState.word[+config.infinityMode]
        menuIDs.push(GM_registerMenuCommand(imLabel, () => { document.getElementById('infinity-toggle-label').click() }))
 
        // Add command to toggle Auto-Start
        const astLabel = menuState.symbol[+config.autoStart] + ' '
                       + ( msgs.menuLabel_autoStart || 'Auto-Start' )
                       + menuState.separator + menuState.word[+config.autoStart]
        menuIDs.push(GM_registerMenuCommand(astLabel, () => {
            saveSetting('autoStart', !config.autoStart)
            notify(( msgs.menuLabel_autoStart || 'Auto-Start' ) + ': '+ menuState.word[+config.autoStart])
            refreshMenu()
        }))
 
        // Add command to toggle visibility of toggle
        const tvLabel = menuState.symbol[+!config.toggleHidden] + ' '
                      + ( msgs.menuLabel_toggleVis || 'Toggle Visibility' )
                      + menuState.separator + menuState.word[+!config.toggleHidden]
        menuIDs.push(GM_registerMenuCommand(tvLabel, () => {
            saveSetting('toggleHidden', !config.toggleHidden)
            navToggleDiv.style.display = config.toggleHidden ? 'none' : 'flex' // toggle visibility
            notify(( msgs.menuLabel_toggleVis || 'Toggle Visibility' ) + ': '+ menuState.word[+!config.toggleHidden])
            refreshMenu()
        }))
 
        // Add command to toggle Auto-Scroll
        const ascLabel = menuState.symbol[+!config.autoScrollDisabled] + ' '
                       + ( msgs.menuLabel_autoScroll || 'Auto-Scroll' )
                       + menuState.separator + menuState.word[+!config.autoScrollDisabled]
        menuIDs.push(GM_registerMenuCommand(ascLabel, () => {
            saveSetting('autoScrollDisabled', !config.autoScrollDisabled)
            notify(( msgs.menuLabel_autoScroll || 'Auto-Scroll' ) + ': '+ menuState.word[+!config.autoScrollDisabled])
            refreshMenu()
        }))
 
        // Add command to set reply language
        const rlLabel = '🌐 ' + ( msgs.menuLabel_replyLang || 'Reply Language' )
                      + menuState.separator + config.replyLanguage
        menuIDs.push(GM_registerMenuCommand(rlLabel, () => {
            while (true) {
                let replyLanguage = prompt(
                    `${ msgs.prompt_updateReplyLang || 'Update reply language' }:`, config.replyLanguage)
                if (replyLanguage === null) break // user cancelled so do nothing
                else if (!/\d/.test(replyLanguage)) {
                    replyLanguage = ( // auto-case for menu/alert aesthetics
                        [2, 3].includes(replyLanguage.length) || replyLanguage.includes('-') ? replyLanguage.toUpperCase()
                        : replyLanguage.charAt(0).toUpperCase() + replyLanguage.slice(1).toLowerCase() )
                    saveSetting('replyLanguage', replyLanguage || config.userLanguage)
                    siteAlert(( msgs.alert_replyLangUpdated || 'Language updated' ) + '!', // title
                        ( msgs.appName || config.appName ) + ' ' // msg
                            + ( msgs.alert_willReplyIn || 'will reply in' ) + ' '
                            + ( replyLanguage || msgs.alert_yourSysLang || 'your system language') + '.')
                    if (config.infinityMode) restartInNewChat() // using new reply language                        
                    refreshMenu() ; break
        }}}))
 
        // Add command to set reply topic
        const re_all = new RegExp('^(' + ( msgs.menuLabel_all || 'all' ) + '|all|any|every)$', 'i'),
              rtLabel = '🧠 ' + ( msgs.menuLabel_replyTopic || 'Reply Topic' ) + menuState.separator
                      + ( re_all.test(config.replyTopic) ? ( msgs.menuLabel_all || 'all' )
                                                         : toTitleCase(config.replyTopic) )
        menuIDs.push(GM_registerMenuCommand(rtLabel, () => {
            const replyTopic = prompt(( msgs.prompt_updateReplyTopic || 'Update reply topic' )
                             + ' (' + ( msgs.prompt_orEnter || 'or enter' ) + ' \'ALL\'):', config.replyTopic)
            if (replyTopic !== null) { // user didn't cancel
                const str_replyTopic = replyTopic.toString()
                saveSetting('replyTopic', !replyTopic || re_all.test(str_replyTopic) ? 'ALL' : str_replyTopic)
                siteAlert(( msgs.alert_replyTopicUpdated || 'Topic updated' ) + '!',
                    ( msgs.appName || config.appName ) + ' '
                        + ( msgs.alert_willAnswer || 'will answer questions' ) + ' '
                        + ( !replyTopic || re_all.test(str_replyTopic)
                              ? msgs.alert_onAllTopics || 'on ALL topics'
                              : (( msgs.alert_onTopicOf || 'on the topic of' ) + ' ' + str_replyTopic ))
                        + '!'
                )
                if (config.infinityMode) { // restart session using new reply topic
                    chatgpt.stop() ; document.getElementById('infinity-toggle-label').click() // toggle off
                    setTimeout(() => { document.getElementById('infinity-toggle-label').click() }, 500) } // toggle on
                refreshMenu()
        }}))
 
        // Add command to change reply interval
        const riLabel = '⌚ ' + ( msgs.menuLabel_replyInt || 'Reply Interval' )
                      + menuState.separator + config.replyInterval + 's'
        menuIDs.push(GM_registerMenuCommand(riLabel, async () => {
            while (true) {
                const replyInterval = prompt(
                    `${ msgs.prompt_updateReplyInt || 'Update reply interval (minimum 5 secs)' }:`, config.replyInterval)
                if (replyInterval === null) break // user cancelled so do nothing
                else if (!isNaN(parseInt(replyInterval, 10)) && parseInt(replyInterval, 10) > 4) { // valid int set
                    saveSetting('replyInterval', parseInt(replyInterval, 10))
                    siteAlert(( msgs.alert_replyIntUpdated || 'Interval updated' ) + '!', // title
                        ( msgs.appName || config.appName ) + ' ' // msg
                            + ( msgs.alert_willReplyEvery || 'will reply every' ) + ' '
                            + replyInterval + ' ' + ( msgs.unit_seconds || 'seconds' ) + '.')
                    if (config.infinityMode) resetInSameChat() // using new reply interval                    
                    refreshMenu() ; break
        }}}))
 
        // Add command to launch About modal
        const aboutLabel = `💡 ${ msgs.menuLabel_about || 'About' } ${ msgs.appName || config.appName }`
        menuIDs.push(GM_registerMenuCommand(aboutLabel, launchAboutModal))
    }
 
    function refreshMenu() {
        if (getUserscriptManager() == 'OrangeMonkey') return
        for (const id of menuIDs) { GM_unregisterMenuCommand(id) } registerMenu()
    }
 
    function launchAboutModal() {
 
        // Show alert
        const chatgptJSver = (/chatgpt-([\d.]+)\.min/.exec(GM_info.script.header) || [null, ''])[1],
              headingStyle = 'font-size: 1.15rem',
              pStyle = 'position: relative ; left: 3px',
              pBrStyle = 'position: relative ; left: 4px ',
              aStyle = 'color: ' + ( chatgpt.isDarkMode() ? '#c67afb' : '#8325c4' ) // purple
        const aboutModalID = siteAlert(
            msgs.appName || config.appName, // title
            `<span style="${ headingStyle }"><b>🏷️ <i>${ msgs.about_version || 'Version' }</i></b>: </span>`
                + `<span style="${ pStyle }">${ GM_info.script.version }</span>\n`
            + `<span style="${ headingStyle }"><b>⚡ <i>${ msgs.about_poweredBy || 'Powered by' }</i></b>: </span>`
                + `<span style="${ pStyle }"><a style="${ aStyle }" href="https://chatgpt.js.org" target="_blank" rel="noopener">`
                + 'chatgpt.js</a>' + ( chatgptJSver ? ( ' v' + chatgptJSver ) : '' ) + '</span>\n'
            + `<span style="${ headingStyle }"><b>📜 <i>${ msgs.about_sourceCode || 'Source code' }</i></b>:</span>\n`
                + `<span style="${ pBrStyle }"><a href="${ config.gitHubURL }" target="_blank" rel="nopener">`
                + config.gitHubURL + '</a></span>',
            [ // buttons
                function checkForUpdates() { updateCheck() },
                function getSupport() { safeWindowOpen(config.supportURL) },
                function leaveAReview() { // show new modal
                    const reviewModalID = chatgpt.alert(( msgs.alert_choosePlatform || 'Choose a Platform' ) + ':', '',
                        [ function greasyFork() { safeWindowOpen(config.greasyForkURL + '/feedback#post-discussion') },
                          function productHunt() { safeWindowOpen(
                              'https://www.producthunt.com/products/chatgpt-infinity/reviews/new') },
                          function alternativeTo() { safeWindowOpen(
                              'https://alternativeto.net/software/chatgpt-infinity/about/') }])
                    const reviewBtns = document.getElementById(reviewModalID).querySelectorAll('button')
                    reviewBtns[0].style.display = 'none' // hide dismiss button
                    reviewBtns[1].textContent = ( // remove spaces from AlternativeTo label
                        reviewBtns[1].textContent.replace(/\s/g, '')) },
                function moreChatGPTapps() { safeWindowOpen('https://github.com/adamlui/chatgpt-apps') }
            ], '', 478 // set width
        )
 
        // Re-format buttons to include emoji + localized label + hide Dismiss button
        for (const button of document.getElementById(aboutModalID).querySelectorAll('button')) {
            if (/updates/i.test(button.textContent)) button.textContent = (
                '🚀 ' + ( msgs.btnLabel_updateCheck || 'Check for Updates' ))
            else if (/support/i.test(button.textContent)) button.textContent = (
                '🧠 ' + ( msgs.btnLabel_getSupport || 'Get Support' ))
            else if (/review/i.test(button.textContent)) button.textContent = (
                '⭐ ' + ( msgs.btnLabel_leaveReview || 'Leave Review' ))
            else if (/apps/i.test(button.textContent)) button.textContent = (
                '🤖 ' + ( msgs.btnLabel_moreApps || 'More ChatGPT Apps' ))
            else button.style.display = 'none' // hide Dismiss button
        }
    }
 
    function updateCheck() {
 
        // Fetch latest meta
        const currentVer = GM_info.script.version
        xhr({
            method: 'GET', url: config.updateURL + '?t=' + Date.now(),
            headers: { 'Cache-Control': 'no-cache' },
            onload: response => { const updateAlertWidth = 377
 
                // Compare versions
                const latestVer = /@version +(.*)/.exec(response.responseText)[1]
                for (let i = 0 ; i < 4 ; i++) { // loop thru subver's
                    const currentSubVer = parseInt(currentVer.split('.')[i], 10) || 0,
                          latestSubVer = parseInt(latestVer.split('.')[i], 10) || 0
                    if (currentSubVer > latestSubVer) break // out of comparison since not outdated
                    else if (latestSubVer > currentSubVer) { // if outdated
 
                        // Alert to update
                        const updateModalID = siteAlert(`🚀 ${ msgs.alert_updateAvail || 'Update available' }!`, // title
                            `${ msgs.alert_newerVer || 'An update to' } ${ config.appName } `
                                + ( msgs.appName || config.appName ) + ' '
                                + `(v${ latestVer }) ${ msgs.alert_isAvail || 'is available' }!  `
                                + '<a target="_blank" rel="noopener" style="font-size: 0.7rem" '
                                    + 'href="' + config.gitHubURL + '/commits/main/greasemonkey/'
                                    + config.updateURL.replace(/.*\/(.*)meta\.js/, '$1user.js') + '"'
                                    + `> ${ msgs.link_viewChanges || 'View changes' }</a>`,
                            function update() { // button
                                safeWindowOpen(config.updateURL.replace('meta.js', 'user.js') + '?t=' + Date.now())
                            }, '', updateAlertWidth
                        )
 
                        // Localize button labels if needed
                        if (!config.userLanguage.startsWith('en')) {
                            const updateAlert = document.querySelector(`[id="${ updateModalID }"]`),
                                  updateBtns = updateAlert.querySelectorAll('button')
                            updateBtns[1].textContent = msgs.btnLabel_update || 'Update'
                            updateBtns[0].textContent = msgs.btnLabel_dismiss || 'Dismiss'
                        }
 
                        return
                }}
 
                // Alert to no update, return to About modal
                siteAlert(( msgs.alert_upToDate || 'Up-to-date' ) + '!', // title
                    `${ msgs.appName || config.appName } (v${ currentVer }) ` // msg
                        + ( msgs.alert_isUpToDate || 'is up-to-date' ) + '!',
                    '', '', updateAlertWidth
                )
                launchAboutModal()
    }})}
 
    function toTitleCase(str) {
        const words = str.toLowerCase().split(' ')
        for (let i = 0 ; i < words.length ; i++) // for each word
            words[i] = words[i][0].toUpperCase() + words[i].slice(1) // title-case it
        return words.join(' ') // join'em back together
    }
 
    // Define FEEDBACK functions
 
    function notify(msg, position = '', notifDuration = '', shadow = '') {
 
        // Strip state word to append colored one later
        const foundState = menuState.word.find(word => msg.includes(word))
        if (foundState) msg = msg.replace(foundState, '')
 
        // Show notification
        chatgpt.notify(`${ config.appSymbol } ${ msg }`, position, notifDuration, shadow || chatgpt.isDarkMode() ? '' : 'shadow')
        const notifs = document.querySelectorAll('.chatgpt-notif'),
              notif = notifs[notifs.length -1]
 
        // Append styled state word
        if (foundState) {
            const styledState = document.createElement('span')
            styledState.style.cssText = `color: ${
                foundState == menuState.word[0] ? '#ef4848 ; text-shadow: rgba(255, 169, 225, 0.44) 2px 1px 5px'
                                                : '#5cef48 ; text-shadow: rgba(255, 250, 169, 0.38) 2px 1px 5px' }`
            styledState.append(foundState) ; notif.append(styledState)
        }
    }
 
    function siteAlert(title = '', msg = '', btns = '', checkbox = '', width = '') {
        return chatgpt.alert(title, msg, btns, checkbox, width )}
 
    // Define UI functions
 
    async function insertToggle() {
 
        // Insert toggle
        const toggleParent = document.querySelector('nav')
        if (!toggleParent.contains(navToggleDiv))
            toggleParent.insertBefore(navToggleDiv, toggleParent.children[1])
 
        // Tweak styles
        navToggleDiv.style.flexGrow = 'unset' // overcome OpenAI .grow
        navToggleDiv.style.paddingLeft = '8px'
        document.getElementById('infinity-toggle-knob-span').style.boxShadow = (
            'rgba(0, 0, 0, .3) 0 1px 2px 0' + ( chatgpt.isDarkMode() ? ', rgba(0, 0, 0, .15) 0 3px 6px 2px' : '' ))
        const navicon = document.getElementById('infinity-toggle-navicon')
        if (navicon) navicon.src = `${ // update navicon color in case scheme changed
            config.mediaHostURL}images/icons/infinity-symbol/`
          + `${ chatgpt.isDarkMode() ? 'white' : 'black' }/icon32.png?${config.latestAssetCommitHash}`
    }
 
    function updateToggleHTML() {
 
        // Create/size/position navicon
        const navicon = document.getElementById('infinity-toggle-navicon') || document.createElement('img')
        navicon.id = 'infinity-toggle-navicon'
        navicon.style.width = navicon.style.height = '1.25rem'
        navicon.style.marginLeft = '2px' ; navicon.style.marginRight = '4px'
 
        // Create/ID/disable/hide/update checkbox
        const toggleInput = document.getElementById('infinity-toggle-input') || document.createElement('input')
        toggleInput.id = 'infinity-toggle-input' ; toggleInput.type = 'checkbox' ; toggleInput.disabled = true
        toggleInput.style.display = 'none' ; toggleInput.checked = config.infinityMode
 
        // Create/ID/stylize switch
        const switchSpan = document.getElementById('infinity-switch-span') || document.createElement('span')
        switchSpan.id = 'infinity-switch-span'
        const switchStyles = {
            position: 'relative', left: `${ chatgpt.browser.isMobile() ? 211 : !firstLink ? 160 : isGPT4oUI ? 147 : 154 }px`,
            backgroundColor: toggleInput.checked ? '#ccc' : '#AD68FF', // init opposite  final color
            bottom: `${ !firstLink ? -0.15 : isFirefox || !isGPT4oUI ? 0.05 : 0 }em`,
            width: '30px', height: '15px', '-webkit-transition': '.4s', transition: '0.4s',  borderRadius: '28px'
        }
        Object.assign(switchSpan.style, switchStyles)
 
        // Create/stylize knob, append to switch
        const knobSpan = document.getElementById('infinity-toggle-knob-span') || document.createElement('span')
        knobSpan.id = 'infinity-toggle-knob-span'
        const knobWidth = 12
        const knobStyles = {
            position: 'absolute', left: '3px', bottom: '1.25px',
            width: `${knobWidth}px`, height: `${knobWidth}px`, content: '""', borderRadius: '28px',
            transform: toggleInput.checked ? // init opposite final pos
                'translateX(0)' : 'translateX(13px) translateY(0)',
            backgroundColor: 'white',  '-webkit-transition': '0.4s', transition: '0.4s'
        }
        Object.assign(knobSpan.style, knobStyles) ; switchSpan.append(knobSpan)
 
        // Create/stylize/fill label
        const toggleLabel = document.getElementById('infinity-toggle-label') || document.createElement('label')
        toggleLabel.id = 'infinity-toggle-label'
        if (!firstLink) { // add font size/weight since no firstLink to borrow from
            toggleLabel.style.fontSize = '0.875rem' ; toggleLabel.style.fontWeight = 600 }
        toggleLabel.style.marginLeft = `-${ !firstLink ? 23 : 41 }px` // left-shift to navicon
        toggleLabel.style.cursor = 'pointer' // add finger cursor on hover
        toggleLabel.style.width = `${ chatgpt.browser.isMobile() ? 201 : isGPT4oUI ? 145 : 148 }px` // to truncate overflown text
        toggleLabel.style.overflow = 'hidden' // to truncate overflown text
        toggleLabel.style.textOverflow = 'ellipsis' // to truncate overflown text
        toggleLabel.innerText = ( msgs.menuLabel_infinityMode || 'Infinity Mode' ) + ' '
                              + ( toggleInput.checked ? ( msgs.state_enabled  || 'enabled' )
                                                      : ( msgs.state_disabled || 'disabled' ))
        // Append elements
        for (const elem of [navicon, toggleInput, switchSpan, toggleLabel]) navToggleDiv.append(elem)
 
        // Update visual state
        navToggleDiv.style.display = config.toggleHidden ? 'none' : 'flex'
        setTimeout(() => {
            switchSpan.style.backgroundColor = toggleInput.checked ? '#ad68ff' : '#ccc'
            switchSpan.style.boxShadow = toggleInput.checked ? '2px 1px 9px #d8a9ff' : 'none'
            knobSpan.style.transform = toggleInput.checked ? 'translateX(13px) translateY(0)' : 'translateX(0)'
        }, 1) // min delay to trigger transition fx
    }
 
    const infinityMode = {
 
        async activate() {
            notify(( msgs.menuLabel_infinityMode || 'Infinity Mode' ) + ': ON')
            if (chatgpt.browser.isMobile() && chatgpt.sidebar.isOn()) chatgpt.sidebar.hide()
            if (!new URL(document.location).pathname.startsWith('/g/')) // not on GPT page
                try { chatgpt.startNewChat() } catch (err) { return } // start new chat
            setTimeout(() => {
                chatgpt.send('Generate a single random question'
                    + ( config.replyLanguage ? ( ' in ' + config.replyLanguage ) : '' )
                    + ( ' on ' + ( config.replyTopic == 'ALL' ? 'ALL topics' : 'the topic of ' + config.replyTopic ))
                    + ' then answer it. Don\'t type anything else.')
            }, 500)
            await chatgpt.isIdle()
            if (config.infinityMode && !infinityMode.isActive) // double-check in case de-activated before scheduled
                infinityMode.isActive = setTimeout(infinityMode.continue, parseInt(config.replyInterval, 10) * 1000)
        },
 
        async continue() {
            if (!config.autoScrollDisabled) try { chatgpt.scrollToBottom() } catch(err) {}
            await chatgpt.isIdle() // before starting delay till next iteration
            chatgpt.send('Do it again.')
            if (infinityMode.isActive) // replace timer
                infinityMode.isActive = setTimeout(infinityMode.continue, parseInt(config.replyInterval, 10) * 1000)
        },
 
        deactivate() {
            chatgpt.stop() ; clearTimeout(infinityMode.isActive) ; infinityMode.isActive = null
            document.getElementById('infinity-toggle-input').checked = false // for window listener
            notify(( msgs.menuLabel_infinityMode || 'Infinity Mode' ) + ': OFF')
            config.infinityMode = false // in case toggled by PV listener
        },
 
        toggle() { config.infinityMode ? infinityMode.activate() : infinityMode.deactivate() }
    }
 
    // Define INTERRUPT functions
 
    function restartInNewChat() {
        chatgpt.stop() ; document.getElementById('infinity-toggle-label').click() // toggle off
        setTimeout(() => { document.getElementById('infinity-toggle-label').click() }, 500) // toggle on
    }
 
    async function resetInSameChat() {
        clearTimeout(infinityMode.isActive) ; infinityMode.isActive = null ; await chatgpt.isIdle()
        if (config.infinityMode && !infinityMode.isActive) // double-check in case de-activated before scheduled
            infinityMode.isActive = setTimeout(infinityMode.continue, parseInt(config.replyInterval, 10) * 1000)
    }
 
    // Run MAIN routine
 
    // Init UI flags
    await Promise.race([chatgpt.isLoaded(), new Promise(resolve => setTimeout(resolve, 5000))]) // initial UI loaded
    await chatgpt.sidebar.isLoaded()
    const isFirefox = chatgpt.browser.isFirefox(),
          isGPT4oUI = document.documentElement.className.includes(' '),
          firstLink = chatgpt.getNewChatLink()
 
    // Add listener to auto-disable Infinity Mode
    if (document.hidden !== undefined) // ...if Page Visibility API supported
        document.onvisibilitychange = () => {
            if (config.infinityMode) {                
                if (document.getElementById('infinity-toggle-label')) // ensure toggle state is accurate
                    document.getElementById('infinity-toggle-label').click()
                else infinityMode.deactivate()
                refreshMenu()
        }}
 
    // Add/update TWEAKS style
    const tweaksStyleUpdated = 20240724 // datestamp of last edit for this file's `tweaksStyle`
    let tweaksStyle = document.getElementById('tweaks-style') // try to select existing style
    if (!tweaksStyle || parseInt(tweaksStyle.getAttribute('last-updated'), 10) < tweaksStyleUpdated) { // if missing or outdated
        if (!tweaksStyle) { // outright missing, create/id/attr/append it first
            tweaksStyle = document.createElement('style') ; tweaksStyle.id = 'tweaks-style'
            tweaksStyle.setAttribute('last-updated', tweaksStyleUpdated.toString())
            document.head.append(tweaksStyle)
        }
        tweaksStyle.innerText = (
            ( chatgpt.isDarkMode() ? '.chatgpt-modal > div { border: 1px solid white }' : '' )
          + '.chatgpt-modal button {'
              + 'font-size: 0.77rem ; text-transform: uppercase ;'
              + 'border-radius: 0 !important ; padding: 5px !important ; min-width: 102px }'
          + '.chatgpt-modal button:hover { transform: scale(1.055) }'
          + '.modal-buttons { margin-left: -13px !important }'
          + '* { scrollbar-width: thin }' // make FF scrollbar skinny to not crop toggle
          + '.sticky div:active, .sticky div:focus {' // post-GPT-4o UI sidebar button container
              + 'transform: none !important }' // disable distracting click zoom effect
        )
    }
 
    // Create NAV TOGGLE div, add styles
    const navToggleDiv = document.createElement('div')
    navToggleDiv.style.height = '37px'
    navToggleDiv.style.margin = '2px 0' // add v-margins
    navToggleDiv.style.userSelect = 'none' // prevent highlighting
    navToggleDiv.style.cursor = 'pointer' // add finger cursor
    updateToggleHTML() // create children
 
    if (firstLink) { // borrow/assign CLASSES from sidebar div
        const firstIcon = firstLink.querySelector('div:first-child'),
              firstLabel = firstLink.querySelector('div:nth-child(2)')
        navToggleDiv.classList.add(...firstLink.classList, ...firstLabel.classList)
        navToggleDiv.querySelector('img')?.classList.add(...firstIcon.classList)
    }
 
    insertToggle()
 
    // Add LISTENER to toggle switch/label/config/menu
    navToggleDiv.onclick = () => {
        const toggleInput = document.getElementById('infinity-toggle-input')
        toggleInput.checked = !toggleInput.checked
        config.infinityMode = toggleInput.checked
        updateToggleHTML() ; refreshMenu()
        infinityMode.toggle()
    }
 
    // Auto-start if enabled
    if (config.autoStart) {
        const navToggle = document.getElementById('infinity-toggle-input')
        if (navToggle) navToggle.parentNode.click()
        else { // activate via infinityMode
            infinityMode.activate() ; config.infinityMode = true ; refreshMenu()
    }}
 
    // Monitor <html> to maintain SIDEBAR TOGGLE VISIBILITY on node changes
    const nodeObserver = new MutationObserver(mutations => { mutations.forEach(mutation => {
        if (mutation.type == 'childList' && mutation.addedNodes.length) insertToggle() })})
    nodeObserver.observe(document.documentElement, { childList: true, subtree: true })
 
})()
长期地址
遇到问题?请前往 GitHub 提 Issues。