// ==UserScript== // @name ChatGPT Auto-Talk 📣 // @name:af ChatGPT Auto-Praat 📣 // @name:am ChatGPT በራስሰር እንጋቢ 📣 // @name:ar ChatGPT التحدث التلقائي 📣 // @name:az ChatGPT Avtomatik-Danışıq 📣 // @name:be ChatGPT Аўта-Размова 📣 // @name:bem ChatGPT Kusogolo Kwati 📣 // @name:bg ChatGPT Авто-Разговор 📣 // @name:bn ChatGPT অটো-টক 📣 // @name:bo ChatGPT རང་འཛུལ་སྐད་ཆེན་པོ 📣 // @name:bs ChatGPT Auto-Razgovor 📣 // @name:ca ChatGPT Auto-Parla 📣 // @name:ceb ChatGPT Auto-Panulti 📣 // @name:ckb ChatGPT ئەوەی-خۆکار 📣 // @name:cs ChatGPT Automatický-Rozhovor 📣 // @name:cy ChatGPT Sgwrsio-Awtomatig 📣 // @name:da ChatGPT Auto-Tale 📣 // @name:de ChatGPT Auto-Sprechen 📣 // @name:dv ChatGPT އޮޓޯ-ކޮމިއުނިކޭޝަން 📣 // @name:dz ChatGPT རང་འཛུལ་སྐད་ཆེན་པོ 📣 // @name:el ChatGPT Αυτόματη-Ομιλία 📣 // @name:eo ChatGPT Aŭtomata-Parolo 📣 // @name:es ChatGPT Auto-Hablar 📣 // @name:et ChatGPT Automaatne-Jutt 📣 // @name:eu ChatGPT Auto-Hizketa 📣 // @name:fa ChatGPT گفتگوی خودکار 📣 // @name:fi ChatGPT Auto-Puhe 📣 // @name:fo ChatGPT Sjálvvirkandi-Prát 📣 // @name:fr ChatGPT Auto-Parler 📣 // @name:fr-CA ChatGPT Auto-Parler 📣 // @name:gd ChatGPT Fèin-Labhairt 📣 // @name:gl ChatGPT Auto-Falar 📣 // @name:gu ChatGPT ઓટો-બોલો 📣 // @name:haw ChatGPT Kūʻokoʻa-ʻŌlelo 📣 // @name:he ChatGPT דיבור-אוטומטי 📣 // @name:hi ChatGPT ऑटो-टॉक 📣 // @name:hr ChatGPT Auto-Razgovor 📣 // @name:ht ChatGPT Auto-Pale 📣 // @name:hu ChatGPT Auto-Beszélgetés 📣 // @name:hy ChatGPT Ինքնաբերաբար-Խոսք 📣 // @name:id ChatGPT Bicara-Otomatis 📣 // @name:is ChatGPT Sjálfvirk-Tal 📣 // @name:it ChatGPT Auto-Parlare 📣 // @name:ja ChatGPT オートトーク 📣 // @name:jv ChatGPT Auto-Omah 📣 // @name:ka ChatGPT ავტო-ლაპარაკი 📣 // @name:kab ChatGPT Awto-Talqeq 📣 // @name:kk ChatGPT Авто-Сөйлеу 📣 // @name:km ChatGPT ការប្រាស្រ័យអគ្គិសនី 📣 // @name:kn ChatGPT ಸ್ವಯಂಚಾಲಿತ-ಮಾತು 📣 // @name:ko ChatGPT 자동-토크 📣 // @name:ku ChatGPT Axê-Raxt 📣 // @name:ky ChatGPT Авто-Сүйлөшүү 📣 // @name:la ChatGPT Auto-Loquens 📣 // @name:lb ChatGPT Auto-Schwätzen 📣 // @name:lo ChatGPT ການເວົ້າອັດຕະໂນມັດ 📣 // @name:lt ChatGPT Auto-Kalba 📣 // @name:lv ChatGPT Auto-Saruna 📣 // @name:mg ChatGPT Auto-Kabary 📣 // @name:mi ChatGPT Aunoa-Kōrero 📣 // @name:mk ChatGPT Авто-Разговор 📣 // @name:ml ChatGPT ഓട്ടോ-സംസാരം 📣 // @name:mn ChatGPT Автомат-Яриа 📣 // @name:ms ChatGPT Auto-Bercakap 📣 // @name:mt ChatGPT Auto-Kellem 📣 // @name:my ChatGPT အလိုအလျောက်-စကားပြော 📣 // @name:ne ChatGPT स्वचालित-वार्ता 📣 // @name:nl ChatGPT Auto-Praten 📣 // @name:no ChatGPT Auto-Snakke 📣 // @name:ny ChatGPT Auto-Kulankhula 📣 // @name:pa ChatGPT ਆਟੋ-ਗੱਲਬਾਤ 📣 // @name:pap ChatGPT Auto-Papia 📣 // @name:pl ChatGPT Auto-Rozmowa 📣 // @name:ps ChatGPT خودکاره-خبري کول 📣 // @name:pt ChatGPT Auto-Falar 📣 // @name:pt-BR ChatGPT Auto-Falar 📣 // @name:rn ChatGPT Auto-Kuvuga 📣 // @name:ro ChatGPT Auto-Vorbire 📣 // @name:ru ChatGPT Авто-Разговор 📣 // @name:rw ChatGPT Ibyoguhindura 📣 // @name:sg ChatGPT Auto-Yângâ 📣 // @name:si ChatGPT ස්වයංක්‍රීය-කතා 📣 // @name:sk ChatGPT Automatický-Rozhovor 📣 // @name:sl ChatGPT Avtomatski-Pogovor 📣 // @name:sm ChatGPT Fetalai-Otometi 📣 // @name:sn ChatGPT Auto-Taurirana 📣 // @name:so ChatGPT Hadal-otomaatig ah 📣 // @name:sr ChatGPT Ауто-Разговор 📣 // @name:sv ChatGPT Auto-Prata 📣 // @name:sw ChatGPT Maongezi-Otomatiki 📣 // @name:ta ChatGPT தானாகவே-பேச்சு 📣 // @name:te ChatGPT ఆటో-మాట 📣 // @name:tg ChatGPT Авто-Гап 📣 // @name:th ChatGPT การสนทนาอัตโนมัติ 📣 // @name:ti ChatGPT እስካምልክቲ-ቃል ኣቐሪቡ 📣 // @name:tk ChatGPT Auto-Dialog 📣 // @name:tn ChatGPT Auto-Matla 📣 // @name:to ChatGPT Fakamatala-Otometi 📣 // @name:tpi ChatGPT Autometik-Tok 📣 // @name:tr ChatGPT Otomatik-Konuşma 📣 // @name:uk ChatGPT Авто-Розмова 📣 // @name:ur ChatGPT خودکار-بات 📣 // @name:uz ChatGPT Avto-Suhbat 📣 // @name:vi ChatGPT Tự-Động-Nói 📣 // @name:xh ChatGPT IyaZenzekelayo-Thetha 📣 // @name:yi ChatGPT אַוטאָ-רעדן 📣 // @name:zh ChatGPT 自动谈话 📣 // @name:zh-CN ChatGPT 自动谈话 📣 // @name:zh-HK ChatGPT 自動談話 📣 // @name:zh-SG ChatGPT 自动谈话 📣 // @name:zh-TW ChatGPT 自動談話 📣 // @name:zu ChatGPT Ukuzenzakalela-Ukhuluma 📣 // @description Automatically play ChatGPT responses // @description:af Speel ChatGPT-antwoorde outomaties af // @description:am ምርመራ ChatGPT ምላሾችን በራስ-ሰር // @description:ar تشغيل ردود ChatGPT تلقائيًا // @description:az ChatGPT cavablarını avtomatik olaraq oynat // @description:be Аўтаматычнае прайграванне адказаў ChatGPT // @description:bem Panga zolingana pa ChatGPT mwankhama // @description:bg Автоматично възпроизвеждане на отговори от ChatGPT // @description:bn ChatGPT প্রতিক্রিয়া স্বয়ংক্রিয়ভাবে প্লে করুন // @description:bo ChatGPT ལྟེ་བཤོལ་རང་འཛུལ་སྐད་སྒྲོན་འགྲོ // @description:bs Automatski reproducirajte odgovore ChatGPT-a // @description:ca Reproduir automàticament les respostes de ChatGPT // @description:ceb Awomatikong pagdula sa mga tubag sa ChatGPT // @description:ckb بەرزکردنی وەڵامەکانی ChatGPT بە شێوەی خۆکار // @description:cs Automatické přehrávání odpovědí ChatGPT // @description:cy Chwarae atebion ChatGPT yn awtomatig // @description:da Afspil ChatGPT-svar automatisk // @description:de ChatGPT-Antworten automatisch abspielen // @description:dv ChatGPT ޖެހިދާން އޮޓޯއެވެއްވިދާނެއް ކުރޭ // @description:dz ChatGPT བརྒྱུད་གཏོང་རང་འཛུལ་སྐད་སྒྲོན་འགྲོ // @description:el Αυτόματη αναπαραγωγή απαντήσεων ChatGPT // @description:eo Aŭtomate ludi ChatGPT-respondojn // @description:es Reproducir automáticamente respuestas de ChatGPT // @description:et ChatGPT vastuste automaatne esitamine // @description:eu ChatGPT erantzunak automatikoki erreproduzitu // @description:fa پخش خودکار پاسخهای ChatGPT // @description:fi Toista ChatGPT-vastaukset automaattisesti // @description:fo Spæl ChatGPT svar sjálvvirkandi // @description:fr Lire automatiquement les réponses de ChatGPT // @description:fr-CA Lire automatiquement les réponses de ChatGPT // @description:gd Cluich gu fèin-ghluasadach freagairtean ChatGPT // @description:gl Reproducir automaticamente as respostas de ChatGPT // @description:gu ChatGPT પ્રતિક્રિયાઓ આપોઆપ વગાડો // @description:haw Hoʻopaʻapaʻa i nā pane ChatGPT ma ka automate // @description:he השמע תגובות ChatGPT באופן אוטומטי // @description:hi ChatGPT प्रतिक्रियाओं को स्वचालित रूप से चलाएं // @description:hr Automatski reproducirajte odgovore ChatGPT-a // @description:ht Jwe repons ChatGPT otomatikman // @description:hu ChatGPT-válaszok automatikus lejátszása // @description:hy Ավտոմատ նվագարկել ChatGPT-ի պատասխանները // @description:id Mainkan respons ChatGPT secara otomatis // @description:is Spilaðu sjálfkrafa svör ChatGPT // @description:it Riprodurre automaticamente le risposte di ChatGPT // @description:ja ChatGPT の回答を自動的に再生する // @description:jv Puter balasan ChatGPT kanthi otomatis // @description:ka ავტომატურად ითამაშეთ ChatGPT პასუხები // @description:kab Melk ikdimen n d-yitran n ChatGPT // @description:kk ChatGPT жауаптарын автоматты түрде ойнату // @description:km លេងការឆ្លើយតបរបស់ ChatGPT ដោយស្វ័យប្រវត្តិ // @description:kn ChatGPT ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಪ್ಲೇ ಮಾಡಿ // @description:ko ChatGPT 응답 자동 재생 // @description:ku Serdana erebeyîyan bi xweka yên ChatGPT bixwe // @description:ky ChatGPT жоопторун автоматтык түрдө ойнотуу // @description:la Lude responsiones ChatGPT automatice // @description:lb ChatGPT-Äntwerten automatesch ofspillen // @description:lo ຄວາມສົມພາດ ChatGPT ໃນຕົວເອງ // @description:lt Automatiškai leisti ChatGPT atsakymus // @description:lv Automātiski atskaņot ChatGPT atbildes // @description:mg Alefaso ho azy ny valintenin'i ChatGPT // @description:mi Purei aunoa i ngā whakautu a ChatGPT // @description:mk Автоматски пуштај одговори на ChatGPT // @description:ml ChatGPT പ്രതികരണങ്ങൾ സ്വയമേവ പ്ലേ ചെയ്യുക // @description:mn ChatGPT хариултыг автоматаар тоглуулах // @description:ms Mainkan tindak balas ChatGPT secara automatik // @description:mt Awtomatikament iddoqq ir-risposti ta' ChatGPT // @description:my ChatGPT ၏တုံ့ပြန်ချက်များကို အလိုအလျောက် ဖွင့်ပါ // @description:ne ChatGPT प्रतिक्रियाहरू स्वचालित रूपमा प्ले गर्नुहोस् // @description:nl Speel ChatGPT-antwoorden automatisch af // @description:no Spill ChatGPT-svar automatisk // @description:ny Sewera mayankho a ChatGPT zokha // @description:pa ChatGPT ਦੇ ਜਵਾਬ ਆਟੋਮੈਟਿਕ ਚਲਾਓ // @description:pap Hunga respuesta di ChatGPT otomátikamente // @description:pl Automatyczne odtwarzanie odpowiedzi ChatGPT // @description:ps د ChatGPT ځوابونه په اوتومات ډول لوبې کړئ // @description:pt Reproduzir automaticamente respostas do ChatGPT // @description:pt-BR Reproduzir automaticamente respostas do ChatGPT // @description:rn Gukina ivyishura vya ChatGPT ku buryo bwikora // @description:ro Redați automat răspunsurile ChatGPT // @description:ru Автоматическое воспроизведение ответов ChatGPT // @description:rw Gukina ibisubizo bya ChatGPT ku buryo bwikora // @description:sg Gbangba ko ye tere ChatGPT makumba mbene // @description:si ChatGPT ප්‍රතිචාර ස්වයංක්‍රීයව ප්ලේ කරන්න // @description:sk Automatické prehrávanie odpovedí ChatGPT // @description:sl Samodejno predvajaj odgovore ChatGPT // @description:sm Taʻa otometi tali a le ChatGPT // @description:sn Tumira otomatiki mhinduro dzeChatGPT // @description:so Si toos ah u ciyaar jawaabaha ChatGPT // @description:sr Аутоматски пуштајте одговоре ChatGPT-а // @description:sv Spela upp ChatGPT-svar automatiskt // @description:sw Chezesha majibu ya ChatGPT kiotomatiki // @description:ta ChatGPT பதில்களை தானாகவே இயக்கவும் // @description:te ChatGPT ప్రతిస్పందనలను ఆటోమేటిక్ గా ప్లే చేయండి // @description:tg Ба таври худкор ҷавобҳои ChatGPT-ро навозед // @description:th เล่นการตอบสนอง ChatGPT โดยอัตโนมัติ // @description:ti እቶም ምላእ ብኣውቶማቲክ ንእሽቶ ChatGPT እቶም እዩ ክንዕዘምብ እዩ // @description:tk ChatGPT jogaplaryny awtomatiki oýnamak // @description:tn Dira karabo tsa ChatGPT ka boitsamaisi // @description:to Tataki ke tali ʻo e ChatGPT ‘i he ngaahi fakamatala faka‘otometiki // @description:tpi Pleiim ol toktok blong ChatGPT olsem otomat // @description:tr ChatGPT yanıtlarını otomatik olarak oynat // @description:uk Автоматично відтворювати відповіді ChatGPT // @description:ur خودکار طور پر ChatGPT جوابات چلائیں // @description:uz ChatGPT javoblarini avtomatik ijro etish // @description:vi Tự động phát các phản hồi của ChatGPT // @description:xh Dlala iimpendulo zeChatGPT ngokuzenzekelayo // @description:yi שפּיל אויטאָמאַטיש ChatGPT ענטפֿערס // @description:zh 自动播放 ChatGPT 的回复 // @description:zh-CN 自动播放 ChatGPT 的回复 // @description:zh-HK 自動播放 ChatGPT 的回覆 // @description:zh-SG 自动播放 ChatGPT 的回复 // @description:zh-TW 自動播放 ChatGPT 的回覆 // @description:zu Dlala izimpendulo ze-ChatGPT ngokuzenzakalela // @author Adam Lui // @namespace https://github.com/adamlui // @version 2026.1.4 // @license MIT // @icon data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22180%22%20height=%22180%22%20fill=%22none%22%3E%3Cstyle%3E:root%7B--primary-fill:%23000;--secondary-fill:%23fff;%7D@media%20(prefers-color-scheme:dark)%7B:root%7B--primary-fill:%23fff;--secondary-fill:%23000;%7D%7D%3C/style%3E%3Cg%20clip-path=%22url(%23a)%22%3E%3Crect%20width=%22180%22%20height=%22180%22%20fill=%22var(--primary-fill)%22%20rx=%2290%22/%3E%3Cg%20clip-path=%22url(%23b)%22%3E%3Cpath%20fill=%22var(--secondary-fill)%22%20d=%22M75.91%2073.628V62.232c0-.96.36-1.68%201.199-2.16l22.912-13.194c3.119-1.8%206.838-2.639%2010.676-2.639%2014.394%200%2023.511%2011.157%2023.511%2023.032%200%20.839%200%201.799-.12%202.758l-23.752-13.914c-1.439-.84-2.879-.84-4.318%200L75.91%2073.627Zm53.499%2044.383v-27.23c0-1.68-.72-2.88-2.159-3.719L97.142%2069.55l9.836-5.638c.839-.48%201.559-.48%202.399%200l22.912%2013.195c6.598%203.839%2011.035%2011.995%2011.035%2019.912%200%209.116-5.397%2017.513-13.915%2020.992v.001Zm-60.577-23.99-9.836-5.758c-.84-.48-1.2-1.2-1.2-2.16v-26.39c0-12.834%209.837-22.55%2023.152-22.55%205.039%200%209.716%201.679%2013.676%204.678L70.993%2055.516c-1.44.84-2.16%202.039-2.16%203.719v34.787-.002Zm21.173%2012.234L75.91%2098.339V81.546l14.095-7.917%2014.094%207.917v16.793l-14.094%207.916Zm9.056%2036.467c-5.038%200-9.716-1.68-13.675-4.678l23.631-13.676c1.439-.839%202.159-2.038%202.159-3.718V85.863l9.956%205.757c.84.48%201.2%201.2%201.2%202.16v26.389c0%2012.835-9.957%2022.552-23.27%2022.552v.001Zm-28.43-26.75L47.72%20102.778c-6.599-3.84-11.036-11.996-11.036-19.913%200-9.236%205.518-17.513%2014.034-20.992v27.35c0%201.68.72%202.879%202.16%203.718l29.989%2017.393-9.837%205.638c-.84.48-1.56.48-2.399%200Zm-1.318%2019.673c-13.555%200-23.512-10.196-23.512-22.792%200-.959.12-1.919.24-2.879l23.63%2013.675c1.44.84%202.88.84%204.32%200l30.108-17.392v11.395c0%20.96-.361%201.68-1.2%202.16l-22.912%2013.194c-3.119%201.8-6.837%202.639-10.675%202.639Zm29.748%2014.274c14.515%200%2026.63-10.316%2029.39-23.991%2013.434-3.479%2022.071-16.074%2022.071-28.91%200-8.396-3.598-16.553-10.076-22.43.6-2.52.96-5.039.96-7.557%200-17.153-13.915-29.99-29.989-29.99-3.239%200-6.358.48-9.477%201.56-5.398-5.278-12.835-8.637-20.992-8.637-14.515%200-26.63%2010.316-29.39%2023.991-13.434%203.48-22.07%2016.074-22.07%2028.91%200%208.396%203.598%2016.553%2010.075%2022.431-.6%202.519-.96%205.038-.96%207.556%200%2017.154%2013.915%2029.989%2029.99%2029.989%203.238%200%206.357-.479%209.476-1.559%205.397%205.278%2012.835%208.637%2020.992%208.637Z%22/%3E%3C/g%3E%3C/g%3E%3Cdefs%3E%3CclipPath%20id=%22a%22%3E%3Cpath%20d=%22M0%200h180v180H0z%22/%3E%3C/clipPath%3E%3CclipPath%20id=%22b%22%3E%3Cpath%20d=%22M29.487%2029.964h121.035v119.954H29.487z%22/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22180%22%20height=%22180%22%20fill=%22none%22%3E%3Cstyle%3E:root%7B--primary-fill:%23000;--secondary-fill:%23fff;%7D@media%20(prefers-color-scheme:dark)%7B:root%7B--primary-fill:%23fff;--secondary-fill:%23000;%7D%7D%3C/style%3E%3Cg%20clip-path=%22url(%23a)%22%3E%3Crect%20width=%22180%22%20height=%22180%22%20fill=%22var(--primary-fill)%22%20rx=%2290%22/%3E%3Cg%20clip-path=%22url(%23b)%22%3E%3Cpath%20fill=%22var(--secondary-fill)%22%20d=%22M75.91%2073.628V62.232c0-.96.36-1.68%201.199-2.16l22.912-13.194c3.119-1.8%206.838-2.639%2010.676-2.639%2014.394%200%2023.511%2011.157%2023.511%2023.032%200%20.839%200%201.799-.12%202.758l-23.752-13.914c-1.439-.84-2.879-.84-4.318%200L75.91%2073.627Zm53.499%2044.383v-27.23c0-1.68-.72-2.88-2.159-3.719L97.142%2069.55l9.836-5.638c.839-.48%201.559-.48%202.399%200l22.912%2013.195c6.598%203.839%2011.035%2011.995%2011.035%2019.912%200%209.116-5.397%2017.513-13.915%2020.992v.001Zm-60.577-23.99-9.836-5.758c-.84-.48-1.2-1.2-1.2-2.16v-26.39c0-12.834%209.837-22.55%2023.152-22.55%205.039%200%209.716%201.679%2013.676%204.678L70.993%2055.516c-1.44.84-2.16%202.039-2.16%203.719v34.787-.002Zm21.173%2012.234L75.91%2098.339V81.546l14.095-7.917%2014.094%207.917v16.793l-14.094%207.916Zm9.056%2036.467c-5.038%200-9.716-1.68-13.675-4.678l23.631-13.676c1.439-.839%202.159-2.038%202.159-3.718V85.863l9.956%205.757c.84.48%201.2%201.2%201.2%202.16v26.389c0%2012.835-9.957%2022.552-23.27%2022.552v.001Zm-28.43-26.75L47.72%20102.778c-6.599-3.84-11.036-11.996-11.036-19.913%200-9.236%205.518-17.513%2014.034-20.992v27.35c0%201.68.72%202.879%202.16%203.718l29.989%2017.393-9.837%205.638c-.84.48-1.56.48-2.399%200Zm-1.318%2019.673c-13.555%200-23.512-10.196-23.512-22.792%200-.959.12-1.919.24-2.879l23.63%2013.675c1.44.84%202.88.84%204.32%200l30.108-17.392v11.395c0%20.96-.361%201.68-1.2%202.16l-22.912%2013.194c-3.119%201.8-6.837%202.639-10.675%202.639Zm29.748%2014.274c14.515%200%2026.63-10.316%2029.39-23.991%2013.434-3.479%2022.071-16.074%2022.071-28.91%200-8.396-3.598-16.553-10.076-22.43.6-2.52.96-5.039.96-7.557%200-17.153-13.915-29.99-29.989-29.99-3.239%200-6.358.48-9.477%201.56-5.398-5.278-12.835-8.637-20.992-8.637-14.515%200-26.63%2010.316-29.39%2023.991-13.434%203.48-22.07%2016.074-22.07%2028.91%200%208.396%203.598%2016.553%2010.075%2022.431-.6%202.519-.96%205.038-.96%207.556%200%2017.154%2013.915%2029.989%2029.99%2029.989%203.238%200%206.357-.479%209.476-1.559%205.397%205.278%2012.835%208.637%2020.992%208.637Z%22/%3E%3C/g%3E%3C/g%3E%3Cdefs%3E%3CclipPath%20id=%22a%22%3E%3Cpath%20d=%22M0%200h180v180H0z%22/%3E%3C/clipPath%3E%3CclipPath%20id=%22b%22%3E%3Cpath%20d=%22M29.487%2029.964h121.035v119.954H29.487z%22/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E // @match *://chatgpt.com/* // @connect cdn.jsdelivr.net // @connect gm.chatgptautotalk.com // @connect raw.githubusercontent.com // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.8.5/dist/chatgpt.min.js#sha256-0NMJgflkEQlWbXxtN4SD+wogV6ss2TY6JAu4A0hmM0k= // @require https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@1e84c2e/assets/js/lib/dom.js/dist/dom.min.js#sha256-xovdxRnmYD/eCgBiGCu5+Vd3+WWIvLUKVtU/MnRueeU= // @resource rpgCSS https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@727feff/assets/styles/rising-particles/dist/gray.min.css#sha256-48sEWzNUGUOP04ur52G5VOfGZPSnZQfrF3szUr4VaRs= // @resource rpwCSS https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@727feff/assets/styles/rising-particles/dist/white.min.css#sha256-6xBXczm7yM1MZ/v0o1KVFfJGehHk47KJjq8oTktH4KE= // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getResourceText // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @noframes // @downloadURL https://gm.chatgptautotalk.com // @updateURL https://gm.chatgptautotalk.com // @homepageURL https://chatgptautotalk.com // @supportURL https://support.chatgptautotalk.com // @contributionURL https://github.com/sponsors/adamlui // ==/UserScript== // NOTE: This script relies on the powerful chatgpt.js library @ https://chatgpt.js.org // © 2023–2026 KudoAI & contributors under the MIT license. (async () => { 'use strict' // Init DATA window.env = { browser: { language: chatgpt.getUserLanguage(), isMobile: chatgpt.browser.isMobile(), isFF: chatgpt.browser.isFirefox() }, scriptManager: { name: (() => { try { return GM_info.scriptHandler } catch (err) { return 'unknown' }})(), version: (() => { try { return GM_info.version } catch (err) { return 'unknown' }})() }, ui: { scheme: getScheme() } } Object.assign(env.browser, { get isCompact() { return innerWidth <= 480 }}) env.scriptManager.supportsTooltips = env.scriptManager.name == 'Tampermonkey' && parseInt(env.scriptManager.version.split('.')[0]) >= 5 window.xhr = typeof GM != 'undefined' && GM.xmlHttpRequest || GM_xmlhttpRequest window.app = { version: GM_info.script.version, chatgptjsVer: /chatgpt\.js@([\d.]+)/.exec(GM_info.scriptMetaStr)[1], commitHashes: { app: '698219e' } // for cached .json } app.urls = { resourceHost: `https://cdn.jsdelivr.net/gh/adamlui/chatgpt-auto-talk@${app.commitHashes.app}` } const remoteData = { app: await new Promise(resolve => xhr({ method: 'GET', url: `${app.urls.resourceHost}/assets/data/app.json`, onload: ({ responseText }) => resolve(JSON.parse(responseText)) })), msgs: await new Promise(resolve => { const msgHostDir = `${app.urls.resourceHost}/greasemonkey/_locales/`, msgLocaleDir = `${ env.browser.language ? env.browser.language.replace('-', '_') : 'en' }/` let msgHref = `${ msgHostDir + msgLocaleDir }messages.json`, msgXHRtries = 0 function fetchMsgs() { xhr({ method: 'GET', url: msgHref, onload: handleMsgs })} function handleMsgs(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 original/region-stripped/EN only msgHref = env.browser.language.includes('-') && msgXHRtries == 1 ? // if regional lang on 1st try... msgHref.replace(/(_locales\/[^_]+)_[^_]+(\/)/, '$1$2') // ...strip region before retrying : `${msgHostDir}en/messages.json` // else use default English messages fetchMsgs() } } fetchMsgs() }) } Object.assign(app, { ...remoteData.app, urls: { ...app.urls, ...remoteData.app.urls }, msgs: remoteData.msgs }) Object.assign(chatgpt.selectors, { btns: { ...chatgpt.selectors.btns, moreOptions: 'button:has(> svg > use[href*="#f6d0e2"])', play: 'div[data-testid*=voice-play]', stop: 'div:has(> svg > path[d^="M10 2.08496C14.3713"])' }, replyDiv: 'div[data-message-author-role]' }) // Init SETTINGS window.config = {} const settings = { controls: { // displays top-to-bottom in toolbar menu autoTalkDisabled: { type: 'toggle', defaultVal: false, label: app.msgs.mode_autoTalk }, outlineText: { type: 'toggle', defaultVal: true, label: app.msgs.menuLabel_outlineText, helptip: app.msgs.helptip_outlineText }, toggleHidden: { type: 'toggle', defaultVal: false, label: app.msgs.menuLabel_toggleVis, helptip: app.msgs.helptip_toggleVis }, playNewRepliesOnly: { type: 'toggle', defaultVal: false, label: app.msgs.menuLabel_playNewRepliesOnly, helptip: app.msgs.helptip_playNewRepliesOnly } }, load(...keys) { keys.flat().forEach(key => config[key] = processKey(key, GM_getValue(`${app.configKeyPrefix}_${key}`, undefined))) function processKey(key, val) { const ctrl = settings.controls?.[key] if (val != undefined && ( // validate stored val (ctrl?.type == 'toggle' && typeof val != 'boolean') || (ctrl?.type == 'slider' && isNaN(parseFloat(val))) )) val = undefined return val ?? (ctrl?.defaultVal ?? (ctrl?.type == 'slider' ? 100 : false)) } }, save(key, val) { GM_setValue(`${app.configKeyPrefix}_${key}`, val) ; config[key] = val }, typeIsEnabled(key) { // for menu labels + notifs to return ON/OFF const reInvertSuffixes = /disabled|hidden/i return reInvertSuffixes.test(key) // flag in control key name && !reInvertSuffixes.test(this.controls[key]?.label || '') // but not in label msg key name ? !config[key] : config[key] // so invert since flag reps opposite type state, else don't } } settings.load(Object.keys(settings.controls)) // Define MENU functions const toolbarMenu = { state: { symbols: ['❌', '✔️'], separator: env.scriptManager.name == 'Tampermonkey' ? ' — ' : ': ', words: [app.msgs.state_off.toUpperCase(), app.msgs.state_on.toUpperCase()] }, refresh() { if (typeof GM_unregisterMenuCommand == 'undefined') return this.entryIDs.forEach(id => GM_unregisterMenuCommand(id)) this.register() }, register() { // Add toggles this.entryIDs = Object.keys(settings.controls).map(key => { const entryData = settings.controls[key] const menuLabel = `${ entryData.symbol || this.state.symbols[+settings.typeIsEnabled(key)] } ${entryData.label} ${ entryData.type == 'toggle' ? this.state.separator + this.state.words[+settings.typeIsEnabled(key)] : entryData.type == 'slider' ? ': ' + config[key] + entryData.labelSuffix || '' : entryData.status ? ` — ${entryData.status}` : '' }` return GM_registerMenuCommand(menuLabel, () => { settings.save(key, !config[key]) ; syncConfigToUI({ updatedKey: key }) notify(`${entryData.label}: ${this.state.words[+settings.typeIsEnabled(key)]}`) }, env.scriptManager.supportsTooltips ? { title: entryData.helptip || ' ' } : undefined) }) // Add About entry this.entryIDs.push(GM_registerMenuCommand( `💡 ${app.msgs.menuLabel_about} ${app.msgs.appName}`, () => modals.open('about'), env.scriptManager.supportsTooltips ? { title: ' ' } : undefined )) } } window.updateCheck = () => { xhr({ method: 'GET', url: `${app.urls.update.gm}?t=${Date.now()}`, headers: { 'Cache-Control': 'no-cache' }, onload: ({ responseText }) => { // Compare versions, alert if update found app.latestVer = /@version +(.*)/.exec(responseText)?.[1] if (app.latestVer) for (let i = 0 ; i < 4 ; i++) { // loop thru subver's const currentSubVer = parseInt(app.version.split('.')[i], 10) || 0, latestSubVer = parseInt(app.latestVer.split('.')[i], 10) || 0 if (currentSubVer > latestSubVer) break // out of comparison since not outdated else if (latestSubVer > currentSubVer) // if outdated return modals.open('update', 'available') } // Alert to no update found, nav back to About modals.open('update', 'unavailable') ; modals.open('about') }}) } // Define FEEDBACK functions function notify(msg, pos = '', notifDuration = '', shadow = '') { // Strip state word to append colored one later const foundState = toolbarMenu.state.words.find(word => msg.includes(word)) if (foundState) msg = msg.replace(foundState, '') // Show notification chatgpt.notify(`${app.symbol} ${msg}`, pos, notifDuration, shadow || env.ui.scheme == 'light') const notif = document.querySelector('.chatgpt-notif:last-child') // Append styled state word if (foundState) { const stateStyles = { on: { light: 'color: #5cef48 ; text-shadow: rgba(255,250,169,0.38) 2px 1px 5px', dark: 'color: #5cef48 ; text-shadow: rgb(55,255,0) 3px 0 10px' }, off: { light: 'color: #ef4848 ; text-shadow: rgba(255,169,225,0.44) 2px 1px 5px', dark: 'color: #ef4848 ; text-shadow: rgba(255, 116, 116, 0.87) 3px 0 9px' } } const styledStateSpan = dom.create.elem('span') styledStateSpan.style.cssText = stateStyles[ foundState == toolbarMenu.state.words[0] ? 'off' : 'on'][env.ui.scheme] styledStateSpan.append(foundState) ; notif.append(styledStateSpan) } } // Define MODAL functions const modals = { stack: [], // of types of undismissed modals class: `${app.slug}-modal`, about() { // Show modal const labelStyles = 'text-transform: uppercase ; font-size: 17px ; font-weight: bold ;' + `color: ${ env.ui.scheme == 'dark' ? 'white' : '#494141' }` const aboutModal = modals.alert( `${app.symbol} ${app.msgs.appName}`, // title `🧠 ${app.msgs.about_author}: ` + `${app.msgs.appAuthor} ` + `${app.msgs.about_and} ` + `${app.msgs.about_contributors}\n` + `🏷️ ${app.msgs.about_version}: ` + `${app.version}\n` + `📜 ${app.msgs.about_openSourceCode}: ` + `` + app.urls.github + '\n' + `🚀 ${app.msgs.about_latestChanges}: ` + `` + `${app.urls.github}/commits\n` + `⚡ ${app.msgs.about_poweredBy}: ` + `chatgpt.js` + ` v${app.chatgptjsVer}`, [ // buttons function checkForUpdates() { updateCheck() }, function getSupport(){}, function discuss(){}, function moreAIextensions(){} ], '', 691 // set width ) // Format text aboutModal.querySelector('h2').style.cssText = ` text-align: center ; font-size: 51px ; line-height: 46px ; padding: 15px 0` aboutModal.querySelector('p').style.cssText = ` text-align: center ; overflow-wrap: anywhere ; margin: ${ env.browser.isCompact ? '6px 0 -16px' : '3px 0 29px' }` // Hack buttons aboutModal.querySelectorAll('button').forEach(btn => { btn.style.cssText = 'height: 58px ; min-width: 136px ; text-align: center' // Replace link buttons w/ clones that don't dismiss modal if (/support|discuss|extensions/i.test(btn.textContent)) { btn.replaceWith(btn = btn.cloneNode(true)) btn.onclick = () => modals.safeWinOpen(app.urls[ btn.textContent.includes(app.msgs.btnLabel_getSupport) ? 'support' : btn.textContent.includes(app.msgs.btnLabel_discuss) ? 'discuss' : 'relatedExtensions' ]) } // Prepend emoji + localize labels if (/updates/i.test(btn.textContent)) btn.textContent = `🚀 ${app.msgs.btnLabel_checkForUpdates}` else if (/support/i.test(btn.textContent)) btn.textContent = `🧠 ${app.msgs.btnLabel_getSupport}` else if (/discuss/i.test(btn.textContent)) btn.textContent = `🗨️ ${app.msgs.btnLabel_discuss}` else if (/extensions/i.test(btn.textContent)) btn.textContent = `🤖 ${app.msgs.btnLabel_moreAIextensions}` // Hide Dismiss button else btn.style.display = 'none' // hide Dismiss button }) return aboutModal }, alert(title = '', msg = '', btns = '', checkbox = '', width = '') { // generic one from chatgpt.alert() const alertID = chatgpt.alert(title, msg, btns, checkbox, width), alert = document.getElementById(alertID).firstChild this.init(alert) // add classes + rising particles bg return alert }, init(modal) { this.stylize() modal.classList.add(this.class) ; modal.parentNode.classList.add(`${this.class}-bg`) dom.addRisingParticles(modal) }, observeRemoval(modal, modalType, modalSubType) { // to maintain stack for proper nav const modalBG = modal.parentNode new MutationObserver(([mutation], obs) => { mutation.removedNodes.forEach(removedNode => { if (removedNode == modalBG) { if (modals.stack[0].includes(modalSubType || modalType)) { // new modal not launched so nav back modals.stack.shift() // remove this modal type from stack 1st const prevModalType = modals.stack[0] if (prevModalType) { // open it modals.stack.shift() // remove type from stack since re-added on open modals.open(prevModalType) } } obs.disconnect() }}) }).observe(modalBG.parentNode, { childList: true, subtree: true }) }, open(modalType, modalSubType) { const modal = modalSubType ? this[modalType][modalSubType]() : this[modalType]() // show modal if (!modal) return // since no div returned this.stack.unshift(modalSubType ? `${modalType}_${modalSubType}` : modalType) // add to stack this.init(modal) // add classes + rising particles bg this.observeRemoval(modal, modalType, modalSubType) // to maintain stack for proper nav }, safeWinOpen(url) { open(url, '_blank', 'noopener') }, // to prevent backdoor vulnerabilities stylize() { const { scheme } = env.ui if (!this.styles?.isConnected) document.head.append(this.styles ||= dom.create.style()) this.styles.textContent = ` .${this.class} { /* modals */ user-select: none ; -webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; font-family: -apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif ; padding: 20px 25px 24px 25px !important ; font-size: 20px ; color: ${ scheme == 'dark' ? 'white' : 'black' } !important ; background-image: linear-gradient(180deg, ${ scheme == 'dark' ? '#99a8a6 -200px, black 200px' : '#b6ebff -296px, white 171px' }) } .${this.class} [class*=modal-close-btn] { position: absolute !important ; float: right ; top: 14px !important ; right: 16px !important ; cursor: pointer ; width: 33px ; height: 33px ; border-radius: 20px } .${this.class} [class*=modal-close-btn] svg { height: 10px } .${this.class} [class*=modal-close-btn] path { ${ scheme == 'dark' ? 'stroke: white ; fill: white' : 'stroke: #9f9f9f ; fill: #9f9f9f' }} ${ scheme == 'dark' ? // invert dark mode hover paths `.${this.class} [class*=modal-close-btn]:hover path { stroke: black ; fill: black }` : '' } .${this.class} [class*=modal-close-btn]:hover { background-color: #f2f2f2 } /* hover underlay */ .${this.class} [class*=modal-close-btn] svg { margin: 11.5px } /* center SVG for hover underlay */ .${this.class} a { color: #${ scheme == 'dark' ? '00cfff' : '1e9ebb' } !important } .${this.class} h2 { font-weight: bold } .${this.class} button { --btn-transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ; font-size: 14px ; text-transform: uppercase ; /* shrink/uppercase labels */ border-radius: 0 !important ; /* square borders */ transition: var(--btn-transition) ; /* smoothen hover fx */ -webkit-transition: var(--btn-transition) ; -moz-transition: var(--btn-transition) ; -o-transition: var(--btn-transition) ; -ms-transition: var(--btn-transition) ; cursor: pointer !important ; /* add finger cursor */ border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } !important ; padding: 8px !important ; min-width: 102px /* resize */ } .${this.class} button:not([class*=primary]) { background: none } .${this.class} button:hover { ${ scheme == 'light' ? // reduce intensity of light scheme hover glow '--btn-shadow: 2px 1px 43px #00cfff70 ;' : '' } color: inherit !important ; /* remove color hack */ background-color: rgb(${ scheme == 'light' ? '192 223 227 / 5%' : '43 156 171 / 43%' }) } ${ env.browser.isMobile ? '' : `.${this.class} .modal-buttons { margin-left: -13px !important }` } .about-em { color: ${ scheme == 'dark' ? 'white' : 'green' } !important }` }, update: { width: 377, available() { // Show modal const updateAvailModal = modals.alert(`🚀 ${app.msgs.alert_updateAvail}!`, // title `${app.msgs.alert_newerVer} ${app.msgs.appName} ` // msg + `(v${app.latestVer}) ${app.msgs.alert_isAvail}! ` + '${app.msgs.link_viewChanges}`, function update() { // button modals.safeWinOpen(`${app.urls.update.gm}?t=${Date.now()}`) }, '', modals.update.width ) // Localize button labels if needed if (!env.browser.language.startsWith('en')) { const updateBtns = updateAvailModal.querySelectorAll('button') updateBtns[1].textContent = app.msgs.btnLabel_update updateBtns[0].textContent = app.msgs.btnLabel_dismiss } return updateAvailModal }, unavailable() { return modals.alert(`${app.msgs.alert_upToDate}!`, // title `${app.msgs.appName} (v${app.version}) ${app.msgs.alert_isUpToDate}!`, // msg '', '', modals.update.width ) } } } // Define MESSAGE functions const message = { get: { lastMoreOptionsBtn() { const moreOptionsBtns = document.querySelectorAll(chatgpt.selectors.btns.moreOptions), lastMoreOptionsBtn = moreOptionsBtns[moreOptionsBtns.length -1] return !lastMoreOptionsBtn?.closest('article') ? null : lastMoreOptionsBtn }, talkingDiv() { return this.lastMoreOptionsBtn()?.closest('article')?.querySelector(chatgpt.selectors.replyDiv) } }, openLastMoreOptionsMenu() { this.get.lastMoreOptionsBtn()?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) }, outline: { hide(targetNode) { if (targetNode) targetNode.style.cssText = '' }, show(targetNode) { if (!config.outlineText || !targetNode) return this.durations ||= { iniDelay: 500 /* ms */, pulse: 5 /* s */ } this.color = `rgba(${ chatgpt.isDarkMode() ? '223,225,227' : '0,157,255' },0.7)` setTimeout(() => targetNode.style.cssText = ` box-shadow: 0 0 0 0 ${this.color} ; animation: pulse-glow ${this.durations.pulse}s ease-in-out ; border-radius: 8px`, this.durations.iniDelay) if (!this.pulseGlowKeyframes?.isConnected) document.head.append(this.pulseGlowKeyframes ||= dom.create.style()) this.pulseGlowKeyframes.textContent = `@keyframes pulse-glow { 0% { box-shadow: 0 0 0 0 ${this.color} } 50% { box-shadow: 0 0 0 8px rgba(59, 130, 246, 0) } 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0) } }` } }, playLast() { this.openLastMoreOptionsMenu() dom.get.loadedElem(chatgpt.selectors.btns.play, { timeout: 1000 }).then(playBtn => { if (!playBtn) return playBtn.click() ; document.querySelector('textarea')?.click() this.outline.show(this.get.talkingDiv()) }) }, async stopPlaying() { this.openLastMoreOptionsMenu() const actionBtn = await Promise.race([ dom.get.loadedElem(chatgpt.selectors.btns.stop), dom.get.loadedElem(chatgpt.selectors.btns.play), new Promise(resolve => setTimeout(resolve, 1000)) ]) if (actionBtn?.matches?.(chatgpt.selectors.btns.stop)) { actionBtn.click() // stop playing if (config.outlineText) this.outline.hide(this.get.talkingDiv()) // rid outline } document.querySelector('textarea')?.click() } } // Define UI functions function getScheme() { return /\b(light|dark)\b/.exec(document.documentElement.className)?.[1] || ( window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' ) } function syncConfigToUI({ updatedKey } = {}) { if (/autoTalkDisabled|toggleHidden/.test(updatedKey)) { toggles.sidebar.update.state() if (updatedKey == 'autoTalkDisabled') message[config.autoTalkDisabled || config.playNewRepliesOnly ? 'stopPlaying' : 'playLast']() } else if (updatedKey == 'outlineText' && !config.outlineText) message.outline.hide(message.get.talkingDiv()) toolbarMenu.refresh() // prefixes/suffixes } const toggles = { sidebar: { class: `${app.slug}-sidebar-toggle`, create() { // Init toggle elems this.div = dom.create.elem('div', { class: this.class }) this.navicon = dom.create.elem('img') this.toggleLabel = dom.create.elem('label') this.switchSpan = dom.create.elem('span') this.knobSpan = dom.create.elem('span') // Assemble elems into parent div this.switchSpan.append(this.knobSpan) this.div.append(this.navicon, this.toggleLabel, this.switchSpan) // Stylize elems this.stylize() // create/append stylesheet // Update scheme/state this.update.scheme() ; this.update.state() // Add hover/click listeners this.div.onmouseover = this.div.onmouseout = ({ type }) => // trigger OpenAI hover overlay this.div.style.setProperty('--item-background-color', `var(--sidebar-surface-${ type == 'mouseover' ? 'secondary' : 'primary' })`) this.div.onclick = () => { settings.save('autoTalkDisabled', !config.autoTalkDisabled) syncConfigToUI({ updatedKey: 'autoTalkDisabled' }) notify(`${app.msgs.mode_autoTalk}: ${toolbarMenu.state.words[+!config.autoTalkDisabled]}`) } }, stylize() { // requires lib/.js + env const firstLink = chatgpt.getNewChatLink() if (firstLink && !this.classesBorrowed) { // borrow/assign classes from sidebar elems const firstIcon = firstLink.querySelector('div:first-child'), firstLabel = firstLink.querySelector('div:nth-child(2)') this.div.classList.add(...firstLink.classList, ...(firstLabel?.classList || [])) this.div.querySelector('img')?.classList.add(...(firstIcon?.classList || [])) this.classesBorrowed = true } this.styles ||= dom.create.style( `:root { /* vars */ --switch-enabled-bg-color: #ad68ff ; --switch-disabled-bg-color: #ccc ; --switch-enabled-box-shadow: 1px 2px 8px #d8a9ff ; --switch-enabled-hover-box-shadow: 0 1px 10px #9b5ad1 ; --knob-box-shadow: rgba(0,0,0,0.3) 0 1px 2px 0 ; --knob-box-shadow-dark: rgba(0,0,0,0.3) 0 1px 2px 0, rgba(0,0,0,0.15) 0 3px 6px 2px }` // Element styles + `.${this.class} { /* parent div */ width: auto ; max-height: 37px ; padding: 0 5px ; user-select: none ; cursor: pointer } .${this.class} > img { /* navicon */ width: 1.25rem ; height: 1.25rem ; margin-left: 2px ; margin-right: 4px ; transition: transform 0.5s ease } .${this.class}:has(span.disabled) > img { /* shrink navicon on toggle off */ transform: scaleY(0.6) } .${this.class} > span { /* switch span */ position: relative ; width: 30px ; height: 15px ; border-radius: 28px ; background-color: var(--switch-disabled-bg-color) ; bottom: ${ firstLink ? '0.5px' : '-0.15em' } ; transition: 0.4s ; -webkit-transition: 0.4s ; -moz-transition: 0.4s ; -o-transition: 0.4s ; -ms-transition: 0.4s } .${this.class} > span.enabled { /* switch on */ background-color: var(--switch-enabled-bg-color) ; box-shadow: var(--switch-enabled-box-shadow) ; -webkit-box-shadow: var(--switch-enabled-box-shadow) ; -moz-box-shadow: var(--switch-enabled-box-shadow) ; transition: 0.15s ; -webkit-transition: 0.15s ; -moz-transition: 0.15s ; -o-transition: 0.15s ; -ms-transition: 0.15s } .${this.class}:hover > span.enabled { /* switch on when hover on parent div */ box-shadow: var(--switch-enabled-hover-box-shadow) ; -webkit-box-shadow: var(--switch-enabled-hover-box-shadow) ; -moz-box-shadow: var(--switch-enabled-hover-box-shadow) } .${this.class} > span.disabled { /* switch off */ background-color: var(--switch-disabled-bg-color) ; box-shadow: none } .${this.class} > span > span { /* knob span */ position: absolute ; width: 12px ; height: 12px ; content: "" ; border-radius: 28px ; background-color: white ; left: 2px ; bottom: 1.25px ; box-shadow: var(--knob-box-shadow) ; -webkit-box-shadow: var(--knob-box-shadow) ; -moz-box-shadow: var(--knob-box-shadow) ; transition: 0.4s ; -webkit-transition: 0.4s ; -moz-transition: 0.4s ; -o-transition: 0.4s ; -ms-transition: 0.4s } .${this.class} > label { /* toggle label */ cursor: pointer ; overflow: hidden ; text-overflow: ellipsis ; white-space: nowrap ; color: black ; padding: 0 3px ; flex-grow: 1 ; font-size: var(--text-sm) }` // Dark scheme mods + `.${this.class}.dark > span.enabled { /* switch on */ background-color: var(--switch-enabled-bg-color) ; box-shadow: var(--switch-enabled-hover-box-shadow) ; /* use hover style instead */ -webkit-box-shadow: var(--switch-enabled-hover-box-shadow) ; -moz-box-shadow: var(--switch-enabled-hover-box-shadow) } .${this.class}.dark:hover > span.enabled { /* switch on when hover on parent div */ box-shadow: var(--switch-enabled-box-shadow) ; /* use regular style instead */ -webkit-box-shadow: var(--switch-enabled-box-shadow) ; -moz-box-shadow: var(--switch-enabled-box-shadow) } .${this.class}.dark > span > span { /* knob span */ box-shadow: var(--knob-box-shadow-dark) ; /* make 3D-er */ -webkit-box-shadow: var(--knob-box-shadow-dark) ; -moz-box-shadow: var(--knob-box-shadow-dark) } .${this.class}.dark > label { color: white } /* toggle label */` ) if (!this.styles.isConnected) document.head.append(this.styles) }, insert() { // requires lib/.min.js const sidebar = document.querySelector(chatgpt.selectors.sidebar) if (!sidebar || this.status?.startsWith('insert') || document.querySelector(`.${this.class}`)) return this.status = 'inserting' ; if (!this.div) this.create() const sidebarHeader = sidebar.querySelector('div#sidebar-header') if (sidebarHeader) { sidebarHeader.after(this.div) ; this.status = 'inserted' } else { this.status = 'waitingForSidebar' dom.get.loadedElem('div#sidebar-header').then(header => { header.after(this.div) ; this.stylize() ; this.status = 'inserted' }).catch((err) => { this.status = 'failed' ; console.error('toggles.sidebar.insert()', err) }) } }, update: { navicon({ preload } = {}) { const baseURL = `${ app.urls.resourceHost.replace(/@\w+/, '@397297d')}/assets/images/icons/soundwave/tall` const schemeMap = { light: 'black', dark: 'white' } if (preload) Object.keys(schemeMap).forEach(scheme => new Image().src = `${baseURL}/${schemeMap[scheme]}.svg`) else toggles.sidebar.navicon.src = `${baseURL}/${schemeMap[env.ui.scheme]}.svg` }, scheme() { // to match UI scheme toggles.sidebar.div.classList.remove('dark', 'light') toggles.sidebar.div.classList.add(env.ui.scheme) toggles.sidebar.update.navicon() }, state() { if (!toggles.sidebar.div) return // since toggle never created = sidebar missing toggles.sidebar.div.style.display = config.toggleHidden ? 'none' : 'flex' const isOn = !config.autoTalkDisabled toggles.sidebar.toggleLabel.textContent = `${app.msgs.mode_autoTalk} ` + app.msgs[`state_${ isOn ? 'enabled' : 'disabled' }`] requestAnimationFrame(() => { toggles.sidebar.switchSpan.className = isOn ? 'enabled' : 'disabled' toggles.sidebar.knobSpan.style.transform = `translateX(${ isOn ? 13 : 0 }px)` }) // to trigger 1st transition fx } } } } // Run MAIN routine toolbarMenu.register() // create browser toolbar menu toggles.sidebar.update.navicon({ preload: true }) // preload sidebar NAVICON variants await Promise.race([chatgpt.isLoaded(), new Promise(resolve => setTimeout(resolve, 5000))]) // initial UI loaded ;['rpg', 'rpw'].forEach(cssType => // add Rising Particles styles document.head.append(dom.create.style(GM_getResourceText(`${cssType}CSS`)))) toggles.sidebar.insert() // Observe body for need to AUTO-PLAY response let locationPath = location.pathname // to track nav const playObserver = new MutationObserver(mutations => { if (config.autoTalkDisabled || !navigator.userActivation?.hasBeenActive // auto-play not allowed by browser || location.search == '?temporary-chat=true' // ChatGPT fails to play msg || document.querySelector(chatgpt.selectors.btns.login) // no Play btn ) return if (locationPath != location.pathname) { // nav'd to diff page, play last msg if applicable locationPath = location.pathname ; if (!/\/c\/[\w-]+$/.test(locationPath)) return if (config.playNewRepliesOnly) { // temp disable observer for options btns to load w/o re-triggers playObserver.disconnect() setTimeout(() => playObserver.observe(document.body, { childList: true, subtree: true }), 1000) } else message.playLast() } else for (const { addedNodes } of mutations) for (const node of addedNodes) { // look for options btns created if ( // ignore all nodes except msg option parents node.tagName == 'DIV' && node.querySelector(chatgpt.selectors.btns.moreOptions)?.closest('article') ) message.playLast() } }) playObserver.observe(document.body, { childList: true, subtree: true }) // Monitor NODE CHANGES to maintain sidebar toggle visibility new MutationObserver(() => { if (!config.toggleHidden && document.querySelector(chatgpt.selectors.sidebar) && !document.querySelector(`.${toggles.sidebar.class}`) && toggles.sidebar.status != 'inserting' ) { toggles.sidebar.status = 'missing' ; toggles.sidebar.insert() } }).observe(document.body, { attributes: true, subtree: true }) // Monitor SCHEME PREF changes to update sidebar toggle + modal colors new MutationObserver(handleSchemePrefChange).observe( // for site scheme pref changes document.documentElement, { attributes: true, attributeFilter: ['class'] }) window.matchMedia('(prefers-color-scheme: dark)').addEventListener( // for browser/system scheme pref changes 'change', () => requestAnimationFrame(handleSchemePrefChange)) function handleSchemePrefChange() { const displayedScheme = getScheme() if (env.ui.scheme != displayedScheme) { env.ui.scheme = displayedScheme ; toggles.sidebar.update.scheme() ; modals.stylize() } } })()