Greasy Fork is available in English.

מחק את היסטוריית ChatGPT 🕶️

מנקה באופן אוטומטי את היסטוריית הצ'אט בעת ביקור ב-chatgpt.com

התקן את הסקריפט?
סקריפטים מומלצים של יוצר זה

אולי תאהב גם את DuckDuckGPT 🤖.

התקן את הסקריפט
  1. // ==UserScript==
  2. // @name Autoclear ChatGPT History
  3. // @name:af Verwyder ChatGPT Geskiedenis 🕶️
  4. // @name:am በተሻለ ChatGPT ጉዳዮ ማግኘት 🕶️
  5. // @name:ar مسح تاريخ ChatGPT 🕶️
  6. // @name:az ChatGPT Tarixini Təmizləyin 🕶️
  7. // @name:be Ачысціць гісторыю ChatGPT 🕶️
  8. // @name:bem Lekeni ChatGPT History 🕶️
  9. // @name:bg Изчистете ChatGPT История 🕶️
  10. // @name:bn চ্যাটজিপিটি ইতিহাস মুছে ফেলুন 🕶️
  11. // @name:bo ChatGPT སྐད་དོན་ཚར་བཟོ། 🕶️
  12. // @name:bs Obriši ChatGPT historiju 🕶️
  13. // @name:ca Esborra l'historial de ChatGPT 🕶️
  14. // @name:ceb Autoclear ChatGPT Kasaysayan 🕶️
  15. // @name:ckb بسڕەوەی مێژووی ChatGPT 🕶️
  16. // @name:cs Vymazat ChatGPT Historii 🕶️
  17. // @name:cy Clirio Hanes ChatGPT 🕶️
  18. // @name:da Ryd ChatGPT Historik 🕶️
  19. // @name:de ChatGPT-Verlauf löschen 🕶️
  20. // @name:dv ChatGPT ހިސްތުކުރުން ފުހެވޭނީ 🕶️
  21. // @name:dz ChatGPT སྐད་ཆ་འབད་བཅུ་ 🕶️
  22. // @name:el Διαγραφή Ιστορικού ChatGPT 🕶️
  23. // @name:eo Forviŝi ChatGPT Historion 🕶️
  24. // @name:es Borrar Historial de ChatGPT 🕶️
  25. // @name:et Kustuta ChatGPT Ajalugu 🕶️
  26. // @name:eu Ezabatu ChatGPT Historia 🕶️
  27. // @name:fa پاک کردن تاریخچه ChatGPT 🕶️
  28. // @name:fi Poista ChatGPT:n historia 🕶️
  29. // @name:fo Strika ChatGPT Søgu 🕶️
  30. // @name:fr Effacer l'historique de ChatGPT 🕶️
  31. // @name:fr-CA Effacer l'historique de ChatGPT 🕶️
  32. // @name:gd Lùghdaich Eachdraidh ChatGPT 🕶️
  33. // @name:gl Limpar Historial de ChatGPT 🕶️
  34. // @name:gu આપો ChatGPT ઇતિહાસ 🕶️
  35. // @name:haw Kāpaki Kākoʻo i ka Moʻolelo o ChatGPT 🕶️
  36. // @name:he מחק את היסטוריית ChatGPT 🕶️
  37. // @name:hi ChatGPT इतिहास को हटाएं 🕶️
  38. // @name:hr Izbriši ChatGPT Povijest 🕶️
  39. // @name:ht Efase ChatGPT Istwa 🕶️
  40. // @name:hu ChatGPT Előzmények Törlése 🕶️
  41. // @name:hy Ջնջել ChatGPT-ի Պատմությունը 🕶️
  42. // @name:id Hapus Riwayat ChatGPT 🕶️
  43. // @name:is Eyða ChatGPT Saga 🕶️
  44. // @name:it Cancella Cronologia ChatGPT 🕶️
  45. // @name:ja ChatGPT の履歴を削除する 🕶️
  46. // @name:jv Hapus Riwayat ChatGPT 🕶️
  47. // @name:ka ChatGPT-ის ისტორიის გასუფთავება 🕶️
  48. // @name:kab Sken ChatGPT Tamɣarit 🕶️
  49. // @name:kk ChatGPT Тарихын Жою 🕶️
  50. // @name:km លុបប្រវត្តិសាស្រ្ត ChatGPT 🕶️
  51. // @name:kn ChatGPT ಇತಿಹಾಸವನ್ನು ಅಳಿಸಿ 🕶️
  52. // @name:ko ChatGPT 기록 지우기 🕶️
  53. // @name:ku Çavkaniya ChatGPTê Paqij bike 🕶️
  54. // @name:ky ChatGPT Тарыхын Жок Кыл 🕶️
  55. // @name:la Eximitte Historiam ChatGPT 🕶️
  56. // @name:lb Läschen ChatGPT Geschicht 🕶️
  57. // @name:lo ລຶບບັນດາຕິນມັກສະບັບ ChatGPT 🕶️
  58. // @name:lt Išvalyti ChatGPT Istoriją 🕶️
  59. // @name:lv Notīrīt ChatGPT Vēsturi 🕶️
  60. // @name:mg Mamafa ny ChatGPT Historique 🕶️
  61. // @name:mi Muku ChatGPT Hītori 🕶️
  62. // @name:mk Избриши го Историјата на ChatGPT 🕶️
  63. // @name:ml ചാറ്റ്‌ജിപിടി ചരിത്രം മായ്ക്കുക 🕶️
  64. // @name:mn ChatGPT Түүхийг устгах 🕶️
  65. // @name:ms Padam Sejarah ChatGPT 🕶️
  66. // @name:mt Ħassar It-Twaħħil ChatGPT 🕶️
  67. // @name:my ဆက်လက် ChatGPT သမိုင်းကို ဖျက်ပစ်နေပါသည် 🕶️
  68. // @name:ne Autoclear ChatGPT इतिहास 🕶️
  69. // @name:nl Wis ChatGPT Geschiedenis 🕶️
  70. // @name:no Autoclear ChatGPT Historie 🕶️
  71. // @name:ny Tikalonso ChatGPT Chisulo 🕶️
  72. // @name:pa ਚੈਟਜੀਪੀਟੀ ਇਤਿਹਾਸ ਮਿਟਾਓ 🕶️
  73. // @name:pap Bula Historia di ChatGPT 🕶️
  74. // @name:pl Wyczyść Historię ChatGPT 🕶️
  75. // @name:ps د ChatGPT د تاریخ پاکول 🕶️
  76. // @name:pt Limpar Histórico do ChatGPT 🕶️
  77. // @name:pt-BR Limpar Histórico do ChatGPT 🕶️
  78. // @name:rn Kwihesha ChatGPT Byinshi 🕶️
  79. // @name:ro Ștergeți Istoricul ChatGPT 🕶️
  80. // @name:ru Очистить Историю ChatGPT 🕶️
  81. // @name:rw Fata ChatGPT Itangazo 🕶️
  82. // @name:sg Mbama ChatGPT Makumbe 🕶️
  83. // @name:si නැවත සංවේදී ChatGPT ඉතිරිකිරීම 🕶️
  84. // @name:sk Vymažte ChatGPT Históriu 🕶️
  85. // @name:sl Počisti Zgodovino ChatGPT 🕶️
  86. // @name:sm Masi ChatGPT Faʻaipoipoga 🕶️
  87. // @name:sn Tirisa ChatGPT Chiremba 🕶️
  88. // @name:so Ka Saar Tareenka ChatGPT 🕶️
  89. // @name:sr Обриши историју ChatGPT-а 🕶️
  90. // @name:sv Rensa ChatGPT Historik 🕶️
  91. // @name:sw Futa Historia ya ChatGPT 🕶️
  92. // @name:ta தானாகவே அழிக்க சேட்ஜிபிடி வரலாற்றை 🕶️
  93. // @name:te ChatGPT చరిత్రను తొలగించు 🕶️
  94. // @name:tg Тозаиши корҳои ChatGPT 🕶️
  95. // @name:th ล้างประวัติศาสตร์ ChatGPT 🕶️
  96. // @name:ti ምርግጋጽ ChatGPT ኣጸዓይ ፈጥር 🕶️
  97. // @name:tk ChatGPT Tarixini Aýyr 🕶️
  98. // @name:tn Futa ChatGPT Tlhahlobo 🕶️
  99. // @name:to Fakatonu ChatGPT History 🕶️
  100. // @name:tpi Kolim ChatGPT Stori 🕶️
  101. // @name:tr ChatGPT Geçmişi Temizle 🕶️
  102. // @name:uk Очистити Історію ChatGPT 🕶️
  103. // @name:ur ChatGPT کی تاریخ صاف کریں 🕶️
  104. // @name:uz ChatGPT Tarixini Tozalash 🕶️
  105. // @name:vi Xóa Lịch Sử ChatGPT 🕶️
  106. // @name:xh Qhipha ChatGPT Isaziso 🕶️
  107. // @name:yi ויסמעקן טשאַטגפּט געשיכטע 🕶️
  108. // @name:zh 自动清除 ChatGPT 历史记录 🕶️
  109. // @name:zh-CN 自动清除 ChatGPT 历史记录 🕶️
  110. // @name:zh-HK 自动清除 ChatGPT 历史记录 🕶️
  111. // @name:zh-SG 自动清除 ChatGPT 历史记录 🕶️
  112. // @name:zh-TW 自动清除 ChatGPT 历史记录 🕶️
  113. // @name:zu Sula ChatGPT Isifundo 🕶️
  114. // @description Auto-clears chat history when visiting chatgpt.com
  115. // @description:af Skoonmaak Chat Geskiedenis wanneer jy chatgpt.com besoek
  116. // @description:am የ chatgpt.com ጸሃይ ታክሲን በማግኘት ታከለው
  117. // @description:ar يقوم تلقائيًا بمسح سجل المحادثات عند زيارة chatgpt.com
  118. // @description:az chatgpt.com-a gedəndə avtomatik olaraq söhbət tarixini təmizləyir
  119. // @description:be Аўтаматычна ачышчае гісторыю чата пры наведванні chatgpt.com
  120. // @description:bem Chibwezache Mphindi Zochitika Pamene Kumatemba chatgpt.com
  121. // @description:bg Автоматично изчиства чат историята при посещение на chatgpt.com
  122. // @description:bn যখন chatgpt.com পরিদর্শন করা হলে অটোমেটিকভাবে চ্যাট ইতিহাস মুছে ফেলে
  123. // @description:bo དཔེར་ན་ chatgpt.com འགྲེལ་བཤད་འདི་ལུ་ སྤྱོད་ཡོད་མི་འདི་བལྟ་མི་བཟོ།
  124. // @description:bs Automatski briše istoriju chata prilikom posjete chatgpt.com
  125. // @description:ca S'elimina automàticament l'historial de xats en visitar chatgpt.com
  126. // @description:ceb Automatic gidut-ana sa kasaysayan sa chat sa pagbisita sa chatgpt.com
  127. // @description:ckb دهستکاریکردنی مێژووی گفتوگۆکان خۆکارانه بۆ سەردانی chatgpt.com
  128. // @description:cs Automaticky vymaže historii chatu při návštěvě chatgpt.com
  129. // @description:cy Mae'n glirio hanes sgwrs yn awtomatig wrth ymweld â chatgpt.com
  130. // @description:da Renser automatisk chatloggen ved besøg på chatgpt.com
  131. // @description:de Löscht den Chatverlauf automatisch beim Besuch von chatgpt.com
  132. // @description:dv chatgpt.com އެކައުންމަދުގެ ޗެކުމު ހުރިހައްދަވާނެ ޗައިންކުރޭ
  133. // @description:dz འཛུགས་མིག་འདི་ལུ་ chatgpt.com འགྲེལ་བཤད་རོགས་བསྐྱེད་ཡོད།
  134. // @description:el Αυτόματη διαγραφή ιστορικού συνομιλίας κατά την επίσκεψη στο chatgpt.com
  135. // @description:eo Memorigo de la babilado aŭtomate malaperas dum vizito ĉe chatgpt.com
  136. // @description:es Borra automáticamente el historial de chat al visitar chatgpt.com
  137. // @description:et Kustutab automaatselt vestluse ajaloo, kui külastate saiti chatgpt.com
  138. // @description:eu Berezgaitasunez ezabatzen du txataren historia chatgpt.com bisitatzen denean
  139. // @description:fa پاک کردن خودکار تاریخچه چت در هنگام بازدید از chatgpt.com
  140. // @description:fi Poistaa keskusteluhistorian automaattisesti käydessä chatgpt.comissa
  141. // @description:fo Auto-rensar spjall søguna tá tú vitjar chatgpt.com
  142. // @description:fr Efface automatiquement l'historique des discussions lors de la visite de chatgpt.com
  143. // @description:fr-CA Efface automatiquement l'historique des discussions lors de la visite de chatgpt.com
  144. // @description:gd Thoir aire bheagachaidh air eachdraidh na còmhraidh nuair a tha thu a' tadhal air chatgpt.com
  145. // @description:gl Limpa automáticamente o historial do chat ao visitar chatgpt.com
  146. // @description:gu chatgpt.com મુકે છે નાતીજે જ્ઞાનવર્ધક ચેટ નો ઇતિહાસ
  147. // @description:haw Mālama haku ʻinoʻino pāhana hoʻohānau mai ana i chatgpt.com
  148. // @description:he מנקה באופן אוטומטי את היסטוריית הצ'אט בעת ביקור ב-chatgpt.com
  149. // @description:hi chatgpt.com पर आवर्तित होने पर चैट इतिहास को स्वचालित रूप से साफ करता है
  150. // @description:hr Automatski briše povijest razgovora prilikom posjeta chatgpt.com
  151. // @description:ht Auto-efase istwa chat la lè vizite chatgpt.com
  152. // @description:hu Automatikusan törli a csevegés előzményeit a chatgpt.com látogatásakor
  153. // @description:hy Պատմությունը ինքնաշխատանքային մաքրում է chatgpt.com այցելելուն դեպի
  154. // @description:id Menghapus otomatis riwayat obrolan saat mengunjungi chatgpt.com
  155. // @description:is Hreinsar sjálfvirkt spjallshönnun þegar heimsókn er gerð á chatgpt.com
  156. // @description:it Cancella automaticamente la cronologia della chat durante la visita a chatgpt.com
  157. // @description:ja chatgpt.com を访れる际に自动的にチャット履歴を消去します
  158. // @description:jv Otomatis ngapus riwayat obrolan nalika ngunjungi chatgpt.com
  159. // @description:ka თავისითად წაშლის ჩათის ისტორიას chatgpt.com-ზე მომსახურების დროს
  160. // @description:kab Ireɣsan Ayren Tisstrir ChatGPT I yur-s achatgpt.com
  161. // @description:kk chatgpt.com-ды көруге бастап автоматты түрде чат тарихын жою
  162. // @description:km លុបសេវាកម្មប្រវត្តិការណ៍ជជែកពីព័ត៌មានមនុស្សកន្លងទៅកាន់ chatgpt.com
  163. // @description:kn chatgpt.com ಸಂದರ್ಶಿಸಿದಾಗ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಚಾಟ್ ಇತಿಹಾಸವನ್ನು ಅಳಿಸುತ್ತದೆ
  164. // @description:ko chatgpt.com 방문 시 채팅 기록을 자동으로 지웁니다
  165. // @description:ku Çêkirina historyaya chatê di hembêzkirina chatgpt.com de
  166. // @description:ky chatgpt.com барысында түр кат жазмаларын автоматтык түрде жок этиш
  167. // @description:la Automate chat historia clearum cum chatgpt.com adibvisam
  168. // @description:lb Läscht automatesch d'Chat-Geschicht wéini een chatgpt.com besicht
  169. // @description:lo ລຶບປະວັດການສົນທະນາໃນຖ້າເຂົ້າຊົມ chatgpt.com
  170. // @description:lt Automatiškai išvalo pokalbių istoriją apsilankius chatgpt.com
  171. // @description:lv Automātiski notīra čata vēsturi, apmeklējot chatgpt.com
  172. // @description:mg Mamafa tsy ampy lalao ny tetikasa vaovao rehefa mitovy amin'ny chatgpt.com
  173. // @description:mi Auto-kōmata e whakakore i te hītori whakawhiti kōrero i te toro ki te chatgpt.com
  174. // @description:mk Автоматски брише историја на разговорот при посета на chatgpt.com
  175. // @description:ml chatgpt.com സന്ദർശിക്കുമ്പോൾ ചാറ്റ് ചരിത്രം ഓട്ടോ-പിന്തുണച്ച് അഴിച്ചുവയ്ക്കുന്നു
  176. // @description:mn chatgpt.com-оос орж ирэх үед чатын түүхийг автоматаар цэвэрлэнэ
  177. // @description:ms Membersihkan sejarah perbualan secara automatik apabila melawat chatgpt.com
  178. // @description:mt Jħassar awtomatikament il-kronoloġija tal-chat meta żżur chatgpt.com
  179. // @description:my chatgpt.com ကိုသွားဖို့အတွက် စကားဝှက်မှတ်တမ်းကို အလိုလိုရွေးချယ်ရန် အလွယ်တကူပြန်ဖွင့်ထားသည်
  180. // @description:ne chatgpt.com मा आएकोमा च्याट इतिहास स्वचालित रूपमा हटाउँछ
  181. // @description:nl Wis automatisch de chatgeschiedenis bij een bezoek aan chatgpt.com
  182. // @description:no Sletter automatisk samtalehistorikk ved besøk på chatgpt.com
  183. // @description:ny Kwatsa makina m'chipatala cha chat pamene kuwona chatgpt.com
  184. // @description:pa chatgpt.com ਦੇ ਦੌਰਾਨ ਚੈਟ ਇਤਿਹਾਸ ਆਟੋਮੈਟਿਕ ਕਲੀਅਰ ਹੋ ਜਾਂਦਾ ਹੈ
  185. // @description:pap Limpieza automático di historial di chat na bishita chatgpt.com
  186. // @description:pl Automatycznie czyści historię czatu podczas odwiedzania chatgpt.com
  187. // @description:ps خودکار تاریخچه چت پاک کول په chatgpt.com کښې
  188. // @description:pt Limpa automaticamente o histórico de bate-papo ao visitar chatgpt.com
  189. // @description:pt-BR Limpa automaticamente o histórico de bate-papo ao visitar chatgpt.com
  190. // @description:rn Guteza inkuru y'ikarita y'imibare igihe utanga chatgpt.com
  191. // @description:ro Șterge automat istoricul chat-ului la vizitarea chatgpt.com
  192. // @description:ru Автоматически очищает историю чата при посещении chatgpt.com
  193. // @description:rw Inyunganira amakuru ya chat inyuma yuko umaze guhamagara chatgpt.com
  194. // @description:sg Auto-kura kolibatalu ya misamba wakari karika kutɔbɔ chatgpt.com
  195. // @description:si chatgpt.com වෙත පැය සඳහා ස්වයංක්‍රීයතාවයින් චැට් ඉතිරිකිරීම මකා දැමුම
  196. // @description:sk Automaticky vymazáva históriu chatu pri návšteve chatgpt.com
  197. // @description:sl Samodejno izbriše zgodovino klepeta ob obisku spletnega mesta chatgpt.com
  198. // @description:sm Automa i le malologa o le sootaga i luma i le chatgpt.com
  199. // @description:sn Inongorora chat history chinayo ipapo uchi chiri kunochinja chatgpt.com
  200. // @description:so Wax ka qaad chatka markii la booqdo chatgpt.com
  201. // @description:sr Аутоматски брише историју чата при посети chatgpt.com
  202. // @description:sv Rensar automatiskt chattens historik vid besök på chatgpt.com
  203. // @description:sw Inaondoa historia ya mazungumzo moja kwa moja wakati wa kutembelea chatgpt.com
  204. // @description:ta chatgpt.com அடுத்தடுத்தப்படும் நேரத்தில் உரையாடல் வரலாறை தானியங்கே நீக்குகிறது
  205. // @description:te chatgpt.com సందర్శించినప్పుడు స్వయంచాలకంగా చాట్ చరిత్రను తొలగిస్తుంది
  206. // @description:tg Тарихи чатро худкор ҷоба кунед, ки chatgpt.com равед
  207. // @description:th ล้างประวัติการสนทนาโดยอัตโนมัติเมื่อเข้าชม chatgpt.com
  208. // @description:ti ኮምፕዩተር እየጠበቀ ጊዜ chatgpt.com የተሰኘውን ችግርን አስፈልጋል
  209. // @description:tk chatgpt.com-a ugradykda çat görnüşini awtomatiki biçimde boşaltýar
  210. // @description:tn Emucisha chat chinyakanyaka nga uwonawo chatgpt.com
  211. // @description:to 'Oku fa'a kovi 'e he ngaahi sipoti fakamatala 'i he falelotu ki he chatgpt.com
  212. // @description:tpi Autometik klia chat histeri long bisitim long chatgpt.com
  213. // @description:tr chatgpt.com'u ziyaret ettiğinizde sohbet geçmişini otomatik olarak temizler
  214. // @description:uk Автоматично очищає історію чату при відвідуванні chatgpt.com
  215. // @description:ur chatgpt.com کے دورے پر چیٹ تاریخ خودکار طور پر صاف کرتا ہے
  216. // @description:uz chatgpt.com saytini tashrif buyurganda chat tarixini avtomatik ravishda o'chiradi
  217. // @description:vi Xóa lịch sử trò chuyện tự động khi ghé thăm chatgpt.com
  218. // @description:xh Ingqalasela ihisitela le-chat uma uzithola chatgpt.com
  219. // @description:yi רומט די פֿונען דער שאַט היסטאָריע ביי בעזוך בקוקן chatgpt.com
  220. // @description:zh 访问 chatgpt.com 时自动清除聊天记录
  221. // @description:zh-CN 访问 chatgpt.com 时自动清除聊天记录
  222. // @description:zh-HK 访问 chatgpt.com 时自动清除聊天记录
  223. // @description:zh-SG 访问 chatgpt.com 时自动清除聊天记录
  224. // @description:zh-TW 访问 chatgpt.com 时自动清除聊天记录
  225. // @description:zu Ziba itshala lokucabanga okuzoshintshwa ngokuzenzakalelayo uma ukubuka chatgpt.com
  226. // @author Adam Lui
  227. // @namespace https://github.com/adamlui
  228. // @version 2024.12.20.2
  229. // @license MIT
  230. // @icon https://media.autoclearchatgpt.com/images/icons/openai/black/icon48.png?a8868ef
  231. // @icon64 https://media.autoclearchatgpt.com/images/icons/openai/black/icon64.png?a8868ef
  232. // @compatible chrome
  233. // @compatible edge
  234. // @compatible firefox
  235. // @compatible opera
  236. // @compatible brave
  237. // @compatible vivaldi
  238. // @compatible librewolf
  239. // @compatible ghost
  240. // @compatible qq
  241. // @match *://chatgpt.com/*
  242. // @match *://chat.openai.com/*
  243. // @connect cdn.jsdelivr.net
  244. // @connect update.greasyfork.org
  245. // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.4.0/dist/chatgpt.min.js#sha256-LfB3mqeB6Xiq2BDub1tn3BtvEiMcaWEp+I094MFpA+Q=
  246. // @resource brsCSS https://assets.aiwebextensions.com/styles/rising-stars/dist/black.min.css?v=0cde30f9ae3ce99ae998141f6e7a36de9b0cc2e7#sha256-4nbm81/JSas4wmxFIdliBBzoEEHRZ057TpzNX1PoQIs=
  247. // @resource wrsCSS https://assets.aiwebextensions.com/styles/rising-stars/dist/white.min.css?v=0cde30f9ae3ce99ae998141f6e7a36de9b0cc2e7#sha256-pW8xWWV6tm8Q6Ms+HWZv6+QzzTLJPyL1DyE18ywpVaE=
  248. // @grant GM_setValue
  249. // @grant GM_getValue
  250. // @grant GM_registerMenuCommand
  251. // @grant GM_unregisterMenuCommand
  252. // @grant GM_getResourceText
  253. // @grant GM_xmlhttpRequest
  254. // @grant GM.xmlHttpRequest
  255. // @noframes
  256. // @homepageURL https://www.autoclearchatgpt.com
  257. // @supportURL https://support.autoclearchatgpt.com
  258. // @contributionURL https://github.com/sponsors/adamlui
  259. // ==/UserScript==
  260.  
  261. // Documentation: https://docs.autoclearchatgpt.com
  262. // NOTE: This script relies on the powerful chatgpt.js library @ https://chatgpt.js.org
  263. // © 2023–2024 KudoAI & contributors under the MIT license.
  264.  
  265. (async () => {
  266.  
  267. // Init ENV context
  268. const env = {
  269. browser: { language: chatgpt.getUserLanguage(), isMobile: chatgpt.browser.isMobile() },
  270. scriptManager: {
  271. name: (() => { try { return GM_info.scriptHandler } catch (err) { return 'unknown' }})(),
  272. version: (() => { try { return GM_info.version } catch (err) { return 'unknown' }})()
  273. }
  274. }
  275. env.browser.isPortrait = env.browser.isMobile && (window.innerWidth < window.innerHeight)
  276. const xhr = typeof GM != 'undefined' && GM.xmlHttpRequest || GM_xmlhttpRequest
  277.  
  278. // Init APP data
  279. const app = {
  280. version: GM_info.script.version, configKeyPrefix: 'autoclearChatGPThistory',
  281. chatgptJSver: /chatgpt\.js@([\d.]+)/.exec(GM_info.scriptMetaStr)[1], urls: {},
  282. latestAssetCommitHash: '03919c8' // for cached app.json + messages.json + navicon in toggles.sidebar.insert()
  283. }
  284. app.urls.assetHost = `https://cdn.jsdelivr.net/gh/adamlui/autoclear-chatgpt-history@${app.latestAssetCommitHash}`
  285. const remoteAppData = await new Promise(resolve => xhr({
  286. method: 'GET', url: `${app.urls.assetHost}/app.json`, onload: resp => resolve(JSON.parse(resp.responseText)) }))
  287. Object.assign(app, { ...remoteAppData, urls: { ...app.urls, ...remoteAppData.urls }})
  288. app.urls.update = app.urls.greasyFork.replace('https://', 'https://update.')
  289. .replace(/(\d+)-?([a-z-]*)$/i, (_, id, name) => `${id}/${ name || 'script' }.meta.js`)
  290. app.msgs = {
  291. appName: app.name,
  292. appAuthor: app.author.name,
  293. appDesc: 'Auto-clears chat history when visiting chatgpt.com',
  294. menuLabel_autoclear: 'Autoclear Chats',
  295. menuLabel_clearNow: 'Clear Chats Now',
  296. menuLabel_toggleVis: 'Toggle Visibility',
  297. menuLabel_modeNotifs: 'Mode Notifications',
  298. menuLabel_about: 'About',
  299. menuLabel_donate: 'Please send a donation',
  300. about_version: 'Version',
  301. about_poweredBy: 'Powered by',
  302. about_sourceCode: 'Source code',
  303. mode_autoclear: 'Auto-Clear',
  304. helptip_clearNow: 'Clear chat history now',
  305. helptip_toggleVis: 'Show Auto-Clear toggle in sidebar',
  306. helptip_modeNotifs: 'Show notifications when toggling modes/settings',
  307. notif_chatsCleared: 'Chat history cleared',
  308. alert_choosePlatform: 'Choose a platform',
  309. alert_updateAvail: 'Update available',
  310. alert_newerVer: 'An update to',
  311. alert_isAvail: 'is available',
  312. alert_upToDate: 'Up-to-date',
  313. alert_isUpToDate: 'is up-to-date',
  314. alert_showYourSupport: 'Show your support',
  315. alert_isOSS: 'is open-source software built & maintained for free through 100% volunteer efforts',
  316. alert_despiteAffliction: 'Despite being severely afflicted by',
  317. alert_longCOVID: 'long COVID',
  318. alert_since2020: 'since 2020',
  319. alert_byDonatingResults: 'by donating, you help me to continue improving, fixing bugs, adding new features, and making the software even better',
  320. alert_yourContrib: 'Your contribution',
  321. alert_noMatterSize: 'no matter the size',
  322. alert_directlySupports: 'directly supports my unpaid efforts to ensure this project remains free and open for all to use',
  323. alert_tyForSupport: 'Thank you for your support',
  324. alert_author: 'author',
  325. btnLabel_moreAIextensions: 'More AI Extensions',
  326. btnLabel_rateUs: 'Rate Us',
  327. btnLabel_getSupport: 'Get Support',
  328. btnLabel_checkForUpdates: 'Check for Updates',
  329. btnLabel_update: 'Update',
  330. btnLabel_dismiss: 'Dismiss',
  331. link_viewChanges: 'View changes',
  332. state_enabled: 'enabled',
  333. state_disabled: 'disabled',
  334. state_on: 'on',
  335. state_off: 'off'
  336. }
  337.  
  338. // LOCALIZE app.msgs for non-English users
  339. if (!env.browser.language.startsWith('en')) {
  340. const localizedMsgs = await new Promise(resolve => {
  341. const msgHostDir = app.urls.assetHost + '/greasemonkey/_locales/',
  342. msgLocaleDir = ( env.browser.language ? env.browser.language.replace('-', '_') : 'en' ) + '/'
  343. let msgHref = msgHostDir + msgLocaleDir + 'messages.json', msgXHRtries = 0
  344. function fetchMsgs() { xhr({ method: 'GET', url: msgHref, onload: handleMsgs })}
  345. function handleMsgs(resp) {
  346. try { // to return localized messages.json
  347. const msgs = JSON.parse(resp.responseText), flatMsgs = {}
  348. for (const key in msgs) // remove need to ref nested keys
  349. if (typeof msgs[key] == 'object' && 'message' in msgs[key])
  350. flatMsgs[key] = msgs[key].message
  351. resolve(flatMsgs)
  352. } catch (err) { // if bad response
  353. msgXHRtries++ ; if (msgXHRtries == 3) return resolve({}) // try original/region-stripped/EN only
  354. msgHref = env.browser.language.includes('-') && msgXHRtries == 1 ? // if regional lang on 1st try...
  355. msgHref.replace(/([^_]+_[^_]+)_[^/]*(\/.*)/, '$1$2') // ...strip region before retrying
  356. : ( msgHostDir + 'en/messages.json' ) // else use default English messages
  357. fetchMsgs()
  358. }
  359. }
  360. fetchMsgs()
  361. })
  362. Object.assign(app.msgs, localizedMsgs)
  363. }
  364.  
  365. // Init SETTINGS
  366. const config = {}
  367. const settings = {
  368.  
  369. controls: { // displays top-to-bottom in toolbar menu
  370. autoclear: { type: 'toggle',
  371. label: app.msgs.menuLabel_autoclear, helptip: app.msgs.appDesc },
  372. clearNow: { type: 'action', symbol: '🧹',
  373. label: app.msgs.menuLabel_clearNow, helptip: app.msgs.helptip_clearNow },
  374. toggleHidden: { type: 'toggle',
  375. label: app.msgs.menuLabel_toggleVis, helptip: app.msgs.helptip_toggleVis },
  376. notifDisabled: { type: 'toggle',
  377. label: app.msgs.menuLabel_modeNotifs, helptip: app.msgs.helptip_modeNotifs }
  378. },
  379.  
  380. load(...keys) {
  381. if (Array.isArray(keys[0])) keys = keys[0] // use 1st array arg, else all comma-separated ones
  382. keys.forEach(key => config[key] = GM_getValue(app.configKeyPrefix + '_' + key, false))
  383. },
  384.  
  385. save(key, val) { GM_setValue(app.configKeyPrefix + '_' + key, val) ; config[key] = val }
  386. }
  387. settings.load('autoclear', 'notifDisabled', 'toggleHidden')
  388.  
  389. // Define MENU functions
  390.  
  391. const menu = {
  392. ids: [], state: {
  393. symbols: ['❌', '✔️'], separator: env.scriptManager.name == 'Tampermonkey' ? ' — ' : ': ',
  394. words: [app.msgs.state_off.toUpperCase(), app.msgs.state_on.toUpperCase()]
  395. },
  396.  
  397. register() {
  398. const tooltipsSupported = env.scriptManager.name == 'Tampermonkey'
  399. && parseInt(env.scriptManager.version.split('.')[0]) >= 5
  400. // Add toggles
  401. Object.keys(settings.controls).forEach(key => {
  402. const settingIsEnabled = config[key] ^ /disabled|hidden/i.test(key)
  403. const menuLabel = `${ settings.controls[key].symbol || menu.state.symbols[+settingIsEnabled] } `
  404. + settings.controls[key].label
  405. + ( settings.controls[key].type == 'toggle' ? menu.state.separator
  406. + menu.state.words[+settingIsEnabled] : '' )
  407. menu.ids.push(GM_registerMenuCommand(menuLabel, () => {
  408. if (settings.controls[key].type == 'toggle') {
  409. settings.save(key, !config[key]) ; syncConfigToUI({ updatedKey: key })
  410. notify(`${settings.controls[key].label}: ${
  411. menu.state.words[+(config[key] ^ /disabled|hidden/i.test(key))]}`)
  412. } else // Clear Now action
  413. clearChatsAndGoHome()
  414. }, tooltipsSupported ? { title: settings.controls[key].helptip || ' ' } : undefined))
  415. })
  416.  
  417. // Add About entry
  418. const aboutLabel = `💡 ${app.msgs.menuLabel_about} ${app.msgs.appName}`
  419. menu.ids.push(GM_registerMenuCommand(aboutLabel, () => modals.open('about'),
  420. tooltipsSupported ? { title: ' ' } : undefined ))
  421.  
  422. // Add Donate entry
  423. const donateLabel = `💖 ${app.msgs.menuLabel_donate}`
  424. menu.ids.push(GM_registerMenuCommand(donateLabel, () => modals.open('donate'),
  425. tooltipsSupported ? { title: ' ' } : undefined ))
  426. },
  427.  
  428. refresh() {
  429. if (typeof GM_unregisterMenuCommand == 'undefined') return
  430. for (const id of menu.ids) { GM_unregisterMenuCommand(id) } menu.register()
  431. }
  432. }
  433.  
  434. function updateCheck() {
  435. xhr({
  436. method: 'GET', url: app.urls.update + '?t=' + Date.now(),
  437. headers: { 'Cache-Control': 'no-cache' },
  438. onload: resp => {
  439.  
  440. // Compare versions, alert if update found
  441. app.latestVer = /@version +(.*)/.exec(resp.responseText)[1]
  442. for (let i = 0 ; i < 4 ; i++) { // loop thru subver's
  443. const currentSubVer = parseInt(app.version.split('.')[i], 10) || 0,
  444. latestSubVer = parseInt(app.latestVer.split('.')[i], 10) || 0
  445. if (currentSubVer > latestSubVer) break // out of comparison since not outdated
  446. else if (latestSubVer > currentSubVer) // if outdated
  447. return modals.open('update', 'available')
  448. }
  449.  
  450. // Alert to no update found, nav back to About
  451. modals.open('update', 'unavailable')
  452. }})
  453. }
  454.  
  455. // Define FEEDBACK functions
  456.  
  457. function notify(msg, pos = '', notifDuration = '', shadow = '') {
  458. if (config.notifDisabled && !msg.includes(app.msgs.menuLabel_modeNotifs)) return
  459.  
  460. // Strip state word to append colored one later
  461. const foundState = menu.state.words.find(word => msg.includes(word))
  462. if (foundState) msg = msg.replace(foundState, '')
  463.  
  464. // Show notification
  465. chatgpt.notify(`${app.symbol} ${msg}`, pos, notifDuration, shadow || chatgpt.isDarkMode() ? '' : 'shadow')
  466. const notif = document.querySelector('.chatgpt-notif:last-child')
  467.  
  468. // Append styled state word
  469. if (foundState) {
  470. const styledStateSpan = document.createElement('span')
  471. styledStateSpan.style.cssText = `color: ${
  472. foundState == menu.state.words[0] ? '#ef4848 ; text-shadow: rgba(255, 169, 225, 0.44) 2px 1px 5px'
  473. : '#5cef48 ; text-shadow: rgba(255, 250, 169, 0.38) 2px 1px 5px' }`
  474. styledStateSpan.append(foundState) ; notif.append(styledStateSpan)
  475. }
  476. }
  477.  
  478. // Define MODAL functions
  479.  
  480. const modals = {
  481. stack: [], // of types of undismissed modals
  482. class: `${app.cssPrefix}-modal`,
  483.  
  484. alert(title = '', msg = '', btns = '', checkbox = '', width = '') { // generic one from chatgpt.alert()
  485. const alertID = chatgpt.alert(title, msg, btns, checkbox, width),
  486. alert = document.getElementById(alertID).firstChild
  487. this.init(alert) // add class/listener/starry bg
  488. return alert
  489. },
  490.  
  491. open(modalType, modalSubType) {
  492. const modal = modalSubType ? this[modalType][modalSubType]() : this[modalType]() // show modal
  493. this.stack.unshift(modalSubType ? `${modalType}_${modalSubType}` : modalType) // add to stack
  494. this.init(modal) // add class/listener/starry bg
  495. this.observeRemoval(modal, modalType, modalSubType) // to maintain stack for proper nav
  496. },
  497.  
  498. init(modal) {
  499. if (!this.styles) this.stylize() // to init/append stylesheet
  500. modal.classList.add(this.class) ; modal.parentNode.classList.add(`${this.class}-bg`) // add classes
  501. fillStarryBG(modal) // add Rising Stars bg
  502. },
  503.  
  504. stylize() {
  505. if (!this.styles) {
  506. this.styles = document.createElement('style') ; this.styles.id = `${this.class}-styles`
  507. document.head.append(this.styles)
  508. }
  509. this.styles.innerText = (
  510. `.${this.class} {` // modals
  511. + 'font-family: -apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto,'
  512. + 'Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif ;'
  513. + 'padding: 20px 25px 24px 25px !important ; font-size: 20px ;'
  514. + `background-image: linear-gradient(180deg, ${
  515. chatgpt.isDarkMode() ? '#99a8a6 -200px, black 200px' : '#b6ebff -296px, white 171px' }) }`
  516. + `.${this.class} [class*="modal-close-btn"] {`
  517. + 'position: absolute !important ; float: right ; top: 14px !important ; right: 16px !important ;'
  518. + 'cursor: pointer ; width: 33px ; height: 33px ; border-radius: 20px }'
  519. + `.${this.class} [class*="modal-close-btn"] svg { height: 10px }`
  520. + `.${this.class} [class*="modal-close-btn"] path {`
  521. + `${ chatgpt.isDarkMode() ? 'stroke: white ; fill: white' : 'stroke: #9f9f9f ; fill: #9f9f9f' }}`
  522. + ( chatgpt.isDarkMode() ? // invert dark mode hover paths
  523. `.${this.class} [class*="modal-close-btn"]:hover path { stroke: black ; fill: black }` : '' )
  524. + `.${this.class} [class*="modal-close-btn"]:hover { background-color: #f2f2f2 }` // hover underlay
  525. + `.${this.class} [class*="modal-close-btn"] svg { margin: 11.5px }` // center SVG for hover underlay
  526. + `.${this.class} a { color: #${ chatgpt.isDarkMode() ? '00cfff' : '1e9ebb' } !important }`
  527. + `.${this.class} h2 { font-weight: bold }`
  528. + `.${this.class} button {`
  529. + 'font-size: 14px ; text-transform: uppercase ;' // shrink/uppercase labels
  530. + 'border-radius: 0 !important ;' // square borders
  531. + 'transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ;' // smoothen hover fx
  532. + 'cursor: pointer !important ;' // add finger cursor
  533. + `border: 1px solid ${ chatgpt.isDarkMode() ? 'white' : 'black' } !important ;`
  534. + 'padding: 8px !important ; min-width: 102px }' // resize
  535. + `.${this.class} button:hover {` // add zoom, re-scheme
  536. + 'transform: scale(1.055) ; color: black !important ;'
  537. + `background-color: #${ chatgpt.isDarkMode() ? '00cfff' : '9cdaff' } !important }`
  538. + ( !env.browser.isMobile ? `.${this.class} .modal-buttons { margin-left: -13px !important }` : '' )
  539. + `.about-em { color: ${ chatgpt.isDarkMode() ? 'white' : 'green' } !important }`
  540. )
  541. },
  542.  
  543. observeRemoval(modal, modalType, modalSubType) { // to maintain stack for proper nav
  544. const modalBG = modal.parentNode
  545. new MutationObserver(([mutation], obs) => {
  546. mutation.removedNodes.forEach(removedNode => { if (removedNode == modalBG) {
  547. if (modals.stack[0].includes(modalSubType || modalType)) { // new modal not launched so nav back
  548. modals.stack.shift() // remove this modal type from stack 1st
  549. const prevModalType = modals.stack[0]
  550. if (prevModalType) { // open it
  551. modals.stack.shift() // remove type from stack since re-added on open
  552. modals.open(prevModalType)
  553. }
  554. }
  555. obs.disconnect()
  556. }})
  557. }).observe(modalBG.parentNode, { childList: true, subtree: true })
  558. },
  559.  
  560. dragHandlers: {
  561. mousedown(event) { // find modal, attach listeners, init XY offsets
  562. if (event.button != 0) return // prevent non-left-click drag
  563. if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
  564. modals.dragHandlers.draggableElem = event.currentTarget
  565. modals.dragHandlers.draggableElem.style.cursor = 'grabbing'
  566. event.preventDefault(); // prevent sub-elems like icons being draggable
  567. ['mousemove', 'mouseup'].forEach(event => document.addEventListener(event, modals.dragHandlers[event]))
  568. const draggableElemRect = modals.dragHandlers.draggableElem.getBoundingClientRect()
  569. modals.dragHandlers.offsetX = event.clientX - draggableElemRect.left +21
  570. modals.dragHandlers.offsetY = event.clientY - draggableElemRect.top +12
  571. },
  572.  
  573. mousemove(event) { // drag modal
  574. if (modals.dragHandlers.draggableElem) {
  575. const newX = event.clientX - modals.dragHandlers.offsetX,
  576. newY = event.clientY - modals.dragHandlers.offsetY
  577. Object.assign(modals.dragHandlers.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
  578. }
  579. },
  580.  
  581. mouseup() { // remove listeners, reset modals.dragHandlers.draggableElem
  582. modals.dragHandlers.draggableElem.style.cursor = 'inherit';
  583. ['mousemove', 'mouseup'].forEach(event =>
  584. document.removeEventListener(event, modals.dragHandlers[event]))
  585. modals.dragHandlers.draggableElem = null
  586. }
  587. },
  588.  
  589. about() {
  590.  
  591. // Show modal
  592. const aboutModal = modals.alert(
  593. `${app.symbol} ${app.msgs.appName}`, // title
  594. `🏷️ ${app.msgs.about_version}: <span class="about-em">${app.version}</span>\n`
  595. + `⚡ ${app.msgs.about_poweredBy}: `
  596. + `<a href="${app.urls.chatgptJS}" target="_blank" rel="noopener">chatgpt.js</a>`
  597. + ` v${app.chatgptJSver}\n`
  598. + `📜 ${app.msgs.about_sourceCode}: `
  599. + `<a href="${app.urls.gitHub}" target="_blank" rel="nopener">`
  600. + app.urls.gitHub + '</a>',
  601. [ // buttons
  602. function checkForUpdates() { updateCheck() },
  603. function getSupport(){},
  604. function rateUs() {},
  605. function moreAIextensions(){}
  606. ], '', 656 // set width
  607. )
  608.  
  609. // Format text
  610. aboutModal.querySelector('h2').style.cssText = (
  611. 'text-align: center ; font-size: 51px ; line-height: 46px ; padding: 15px 0' )
  612. aboutModal.querySelector('p').style.cssText = (
  613. 'text-align: center ; overflow-wrap: anywhere ;'
  614. + `margin: ${ env.browser.isPortrait ? '6px 0 -16px' : '3px 0 0' }` )
  615.  
  616. // Hack buttons
  617. aboutModal.querySelectorAll('button').forEach(btn => {
  618. btn.style.cssText = 'height: 58px ; min-width: 136px ; text-align: center'
  619.  
  620. // Replace link buttons w/ clones that don't dismiss modal
  621. if (/support|rate|extensions/i.test(btn.textContent)) {
  622. const btnClone = btn.cloneNode(true)
  623. btn.parentNode.replaceChild(btnClone, btn) ; btn = btnClone
  624. btn.onclick = () => modals.safeWinOpen(
  625. btn.textContent.includes(app.msgs.btnLabel_getSupport) ? app.urls.support
  626. : btn.textContent.includes(app.msgs.btnLabel_rateUs) ? app.urls.review.greasyFork
  627. : app.urls.relatedExtensions
  628. )
  629. }
  630.  
  631. // Prepend emoji + localize labels
  632. if (/updates/i.test(btn.textContent))
  633. btn.textContent = `🚀 ${app.msgs.btnLabel_checkForUpdates}`
  634. else if (/support/i.test(btn.textContent))
  635. btn.textContent = `🧠 ${app.msgs.btnLabel_getSupport}`
  636. else if (/rate/i.test(btn.textContent))
  637. btn.textContent = `⭐ ${app.msgs.btnLabel_rateUs}`
  638. else if (/extensions/i.test(btn.textContent))
  639. btn.textContent = `🤖 ${app.msgs.btnLabel_moreAIextensions}`
  640.  
  641. // Hide Dismiss button
  642. else btn.style.display = 'none' // hide Dismiss button
  643. })
  644.  
  645. return aboutModal
  646. },
  647.  
  648. donate() {
  649.  
  650. // Show modal
  651. const donateModal = modals.alert(
  652. `💖 ${app.msgs.alert_showYourSupport}`, // title
  653. `<p>${app.msgs.appName} ${app.msgs.alert_isOSS}.</p>`
  654. + `<p>${app.msgs.alert_despiteAffliction} `
  655. + '<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Long_COVID">'
  656. + `${app.msgs.alert_longCOVID}</a> `
  657. + `${app.msgs.alert_since2020}, ${app.msgs.alert_byDonatingResults}.</p>`
  658. + `<p>${app.msgs.alert_yourContrib}, <b>${app.msgs.alert_noMatterSize}</b>, `
  659. + `${app.msgs.alert_directlySupports}.</p>`
  660. + `<p>${app.msgs.alert_tyForSupport}!</p>`
  661. + '<img src="https://cdn.jsdelivr.net/gh/adamlui/adamlui/images/siggie/'
  662. + `${ chatgpt.isDarkMode() ? 'white' : 'black' }.png" `
  663. + 'style="height: 54px ; margin: 5px 0 -2px 5px"></img>'
  664. + `<p>—<b><a target="_blank" rel="noopener" href="${app.author.url}">`
  665. + `${app.msgs.appAuthor}</a></b>, ${app.msgs.alert_author}</p>`,
  666. [ // buttons
  667. function paypal(){},
  668. function githubSponsors(){},
  669. function cashApp(){},
  670. function rateUs(){}
  671. ], '', 478 // set width
  672. )
  673.  
  674. // Format text
  675. donateModal.querySelectorAll('p').forEach(p => // v-pad text, shrink line height
  676. p.style.cssText = 'padding: 8px 0 ; line-height: 20px')
  677.  
  678. // Hack buttons
  679. const btns = donateModal.querySelectorAll('button')
  680. btns.forEach((btn, idx) => {
  681.  
  682. // Replace link buttons w/ clones that don't dismiss modal
  683. if (!/dismiss/i.test(btn.textContent)) {
  684. const btnClone = btn.cloneNode(true)
  685. btn.parentNode.replaceChild(btnClone, btn) ; btn = btnClone
  686. btn.onclick = () => modals.safeWinOpen(
  687. btn.textContent == 'Cash App' ? app.urls.donate.cashApp
  688. : btn.textContent == 'Github Sponsors' ? app.urls.donate.gitHub
  689. : btn.textContent == 'Paypal' ? app.urls.donate.payPal
  690. : app.urls.review.greasyFork
  691. )
  692. }
  693.  
  694. // Format buttons
  695. if (idx == 0) btn.style.display = 'none' // hide Dismiss button
  696. else {
  697. btn.style.cssText = 'padding: 8px 6px !important ; margin-top: -14px ;'
  698. + ' width: 107px ; line-height: 14px'
  699. if (idx == btns.length -1) // de-emphasize right-most button
  700. btn.classList.remove('primary-modal-btn')
  701. else if (/rate/i.test(btn.textContent)) // localize 'Rate Us' label
  702. btn.textContent = app.msgs.btnLabel_rateUs
  703. }
  704. })
  705.  
  706. return donateModal
  707. },
  708.  
  709. update: {
  710. width: 377,
  711.  
  712. available() {
  713.  
  714. // Show modal
  715. const updateAvailModal = modals.alert(`🚀 ${app.msgs.alert_updateAvail}!`, // title
  716. `${app.msgs.alert_newerVer} ${app.msgs.appName} `
  717. + `(v${app.latestVer}) ${app.msgs.alert_isAvail}! `
  718. + '<a target="_blank" rel="noopener" style="font-size: 0.7rem" href="'
  719. + app.urls.update.replace(/.+\/([^/]+)meta\.js/,
  720. `${app.urls.gitHub}/commits/main/greasemonkey/$1user.js`)
  721. + `">${app.msgs.link_viewChanges}</a>`,
  722. function update() { // button
  723. modals.safeWinOpen(app.urls.update.replace('meta.js', 'user.js') + '?t=' + Date.now())
  724. }, '', modals.update.width
  725. )
  726.  
  727. // Localize button labels if needed
  728. if (!env.browser.language.startsWith('en')) {
  729. const updateBtns = updateAvailModal.querySelectorAll('button')
  730. updateBtns[1].textContent = app.msgs.btnLabel_update
  731. updateBtns[0].textContent = app.msgs.btnLabel_dismiss
  732. }
  733.  
  734. return updateAvailModal
  735. },
  736.  
  737. unavailable() {
  738. return modals.alert(`${app.msgs.alert_upToDate}!`, // title
  739. `${app.msgs.appName} (v${app.version}) ${app.msgs.alert_isUpToDate}!`, // msg
  740. '', '', modals.update.width
  741. )
  742. }
  743. },
  744.  
  745. safeWinOpen(url) { open(url, '_blank', 'noopener') } // to prevent backdoor vulnerabilities
  746. }
  747.  
  748. // Define UI functions
  749.  
  750. function syncConfigToUI(options) {
  751. if (options?.updatedKey == 'autoclear' && config.autoclear) clearChatsAndGoHome()
  752. if (/autoclear|toggleHidden/.test(options?.updatedKey)) toggles.sidebar.update.state()
  753. menu.refresh() // prefixes/suffixes
  754. }
  755.  
  756. function createStyle(content) {
  757. const style = document.createElement('style')
  758. if (content) style.innerText = content
  759. return style
  760. }
  761.  
  762. function fillStarryBG(targetNode) { // requires https://assets.aiwebextensions.com/styles/rising-stars/css/<black|white>.min.css
  763. if (targetNode.querySelector('[id*="stars"]')) return
  764. const starsDivsContainer = document.createElement('div')
  765. starsDivsContainer.style.cssText = 'position: absolute ; top: 0 ; left: 0 ;' // hug targetNode's top-left corner
  766. + 'height: 100% ; width: 100% ; border-radius: 15px ; overflow: clip ;' // bound innards exactly by targetNode
  767. + 'z-index: -1'; // allow interactive elems to be clicked
  768. ['sm', 'med', 'lg'].forEach(starSize => {
  769. const starsDiv = document.createElement('div')
  770. starsDiv.id = `${ chatgpt.isDarkMode() ? 'white' : 'black' }-stars-${starSize}`
  771. starsDivsContainer.append(starsDiv)
  772. })
  773. targetNode.prepend(starsDivsContainer)
  774. }
  775.  
  776. const toggles = {
  777.  
  778. sidebar: {
  779. ids: { navicon: `${app.cssPrefix}-toggle-navicon`, knobSpan: `${app.cssPrefix}-toggle-knob-span` },
  780.  
  781. create() {
  782. this.div = document.createElement('div')
  783.  
  784. // Create/ID/size/position navicon
  785. const navicon = document.createElement('img') ; navicon.id = this.ids.navicon
  786. navicon.style.cssText = 'width: 1.25rem ; height: 1.25rem ; margin-left: 2px ; margin-right: 4px'
  787.  
  788. // Create/disable/hide checkbox
  789. const toggleInput = document.createElement('input')
  790. Object.assign(toggleInput, { type: 'checkbox', disabled: true })
  791. toggleInput.style.display = 'none'
  792.  
  793. // Create/stylize switch
  794. const switchSpan = document.createElement('span')
  795. Object.assign(switchSpan.style, {
  796. position: 'relative', left: `${ env.browser.isMobile ? 169 : !env.ui.firstLink ? 160 : 154 }px`,
  797. bottom: `${ !env.ui.firstLink ? -0.15 : 0 }em`, width: '30px', height: '15px',
  798. backgroundColor: config.autoclear ? '#ccc' : '#AD68FF', // init opposite final color
  799. '-webkit-transition': '.4s', transition: '0.4s', borderRadius: '28px'
  800. })
  801.  
  802. // Create/stylize knob, append to switch
  803. const knobSpan = document.createElement('span') ; knobSpan.id = toggles.sidebar.ids.knobSpan
  804. Object.assign(knobSpan.style, {
  805. position: 'absolute', left: '3px', bottom: '1.25px',
  806. width: '12px', height: '12px', content: '""', borderRadius: '28px',
  807. transform: config.autoclear ? // init opposite final pos
  808. 'translateX(0)' : 'translateX(13px) translateY(0)',
  809. backgroundColor: 'white', '-webkit-transition': '0.4s', transition: '0.4s'
  810. }) ; switchSpan.append(knobSpan)
  811.  
  812. // Create/stylize/fill label
  813. const toggleLabel = document.createElement('label')
  814. if (!env.ui.firstLink) // add font size/weight since no env.ui.firstLink to borrow from
  815. toggleLabel.style.cssText = 'font-size: 0.875rem, font-weight: 600'
  816. Object.assign(toggleLabel.style, {
  817. marginLeft: `-${ !env.ui.firstLink ? 23 : 41 }px`, // left-shift to navicon
  818. cursor: 'pointer', // add finger cursor on hover
  819. width: `${ env.browser.isMobile ? 201 : 148 }px`, // to truncate overflown text
  820. overflow: 'hidden', textOverflow: 'ellipsis' // to truncate overflown text
  821. })
  822.  
  823. // Append elements
  824. this.div.append(navicon, toggleInput, switchSpan, toggleLabel)
  825.  
  826. // Stylize/classify master div
  827. this.div.style.cssText += (
  828. 'max-height: 37px ; margin: 2px 0 ; user-select: none ; cursor: pointer'
  829. + 'flex-grow: unset' // overcome OpenAI .grow
  830. )
  831. if (env.ui.firstLink) { // borrow/assign classes from sidebar elems
  832. const firstIcon = env.ui.firstLink.querySelector('div:first-child'),
  833. firstLabel = env.ui.firstLink.querySelector('div:nth-child(2)')
  834. this.div.classList.add(...env.ui.firstLink.classList, ...(firstLabel?.classList || []))
  835. this.div.querySelector('img')?.classList.add(...(firstIcon?.classList || []))
  836. }
  837.  
  838. // Update color/state
  839. this.update.color() ; this.update.state() // to opposite init state for animation on 1st load
  840.  
  841. // Add listeners
  842. this.div.onmouseover = this.div.onmouseout = event =>
  843. this.div.style.setProperty('--item-background-color',
  844. `var(--sidebar-surface-${event.type == 'mouseover' ? 'secondary' : 'primary'})`)
  845. this.div.onclick = () => {
  846. settings.save('autoclear', !toggleInput.checked) ; syncConfigToUI({ updatedKey: 'autoclear' })
  847. notify(`${app.msgs.mode_autoclear}: ${menu.state.words[+config.autoclear]}`)
  848. }
  849. },
  850.  
  851. insert() {
  852. if (this.status?.startsWith('insert') || document.getElementById(this.ids.navicon)) return
  853. const sidebar = document.querySelectorAll('nav')[env.browser.isMobile ? 1 : 0] ; if (!sidebar) return
  854. this.status = 'inserting' ; if (!this.div) this.create()
  855. sidebar.insertBefore(this.div, sidebar.children[1]) ; this.status = 'inserted'
  856. },
  857.  
  858. update: {
  859. color() {
  860. const knobSpan = toggles.sidebar.div.querySelector(`#${toggles.sidebar.ids.knobSpan}`),
  861. navicon = toggles.sidebar.div.querySelector(`#${toggles.sidebar.ids.navicon}`)
  862. knobSpan.style.boxShadow = 'rgba(0, 0, 0, .3) 0 1px 2px 0'
  863. + ( chatgpt.isDarkMode() ? ', rgba(0, 0, 0, .15) 0 3px 6px 2px' : '' )
  864. navicon.src = `${app.urls.mediaHost}/images/icons/incognito/${
  865. chatgpt.isDarkMode() ? 'white' : 'black' }/icon32.png?${app.latestAssetCommitHash}`
  866. },
  867.  
  868. state() {
  869. if (!toggles.sidebar.div) return // since toggle never created = sidebar missing
  870. const toggleLabel = toggles.sidebar.div.querySelector('label'),
  871. toggleInput = toggles.sidebar.div.querySelector('input'),
  872. switchSpan = toggles.sidebar.div.querySelector('span'),
  873. knobSpan = switchSpan.firstChild
  874. toggles.sidebar.div.style.display = config.toggleHidden ? 'none' : 'flex'
  875. toggleInput.checked = config.autoclear
  876. toggleLabel.innerText = `${app.msgs.mode_autoclear} ${
  877. app.msgs['state_' + ( toggleInput.checked ? 'enabled' : 'disabled' )]}`
  878. setTimeout(() => {
  879. switchSpan.style.backgroundColor = toggleInput.checked ? '#ad68ff' : '#ccc'
  880. switchSpan.style.boxShadow = toggleInput.checked ? '2px 1px 9px #d8a9ff' : 'none'
  881. knobSpan.style.transform = toggleInput.checked ? 'translateX(13px) translateY(0)' : 'translateX(0)'
  882. }, 1) // min delay to trigger transition fx
  883. }
  884. }
  885. }
  886. }
  887.  
  888. function clearChatsAndGoHome() {
  889. chatgpt.clearChats()
  890.  
  891. // Hide history from DOM since chatgpt.clearChats() works back-end only (front-end updates on reload otherwise)
  892. new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
  893. document.querySelectorAll('nav ol').forEach(ol => {
  894. ol.previousElementSibling.style.display = 'none' // hide temporal heading
  895. ol.querySelectorAll('li').forEach(li => li.style.display = 'none') // hide chat entry
  896. })
  897. if (!clearChatsAndGoHome.historyObserver) { // monitor sidebar to restore temporal headings on new chats
  898. clearChatsAndGoHome.historyObserver = new MutationObserver(mutations => mutations.forEach(mutation => {
  899. if (mutation.type == 'childList') mutation.addedNodes.forEach(node => {
  900. if (node.tagName == 'LI') node.closest('ol').previousElementSibling.style.display = 'inherit'
  901. })}))
  902. clearChatsAndGoHome.historyObserver.observe(
  903. document.querySelector('nav'), { childList: true, subtree: true })
  904. }
  905. })
  906.  
  907. chatgpt.startNewChat() // return home from potential ghost chat
  908. notify(app.msgs.notif_chatsCleared, 'bottom-right', 2.5)
  909. }
  910.  
  911. // Run MAIN routine
  912.  
  913. menu.register() // create browser toolbar menu
  914.  
  915. // Init UI props
  916. await Promise.race([chatgpt.isLoaded(), new Promise(resolve => setTimeout(resolve, 5000))]) // initial UI loaded
  917. await chatgpt.sidebar.isLoaded()
  918. env.ui = { firstLink: chatgpt.getNewChatLink() };
  919.  
  920. // Add STARS styles
  921. ['brs', 'wrs'].forEach(cssType => document.head.append(createStyle(GM_getResourceText(`${cssType}CSS`))))
  922.  
  923. toggles.sidebar.insert()
  924.  
  925. // AUTO-CLEAR on first visit if enabled
  926. if (config.autoclear) clearChatsAndGoHome()
  927.  
  928. // Monitor NODE CHANGES to maintain sidebar toggle visibility
  929. new MutationObserver(() => {
  930. if (!config.toggleHidden && !document.getElementById(toggles.sidebar.ids.navicon)
  931. && toggles.sidebar.status != 'inserting') {
  932. toggles.sidebar.status = 'missing' ; toggles.sidebar.insert() }
  933. }).observe(document.body, { attributes: true, subtree: true })
  934.  
  935. // Monitor SCHEME CHANGES to update sidebar toggle + modal colors
  936. new MutationObserver(() => { toggles.sidebar.update.color() ; modals.stylize() })
  937. .observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
  938.  
  939. // Disable distracting SIDEBAR CLICK-ZOOM effect
  940. if (!document.documentElement.hasAttribute('sidebar-click-zoom-observed')) {
  941. new MutationObserver(mutations => mutations.forEach(({ target }) => {
  942. if (target.closest('[class*="sidebar"]') // include sidebar divs
  943. && !target.id.endsWith('-knob-span') // exclude our toggles.sidebar
  944. && target.style.transform != 'none' // click-zoom occurred
  945. ) target.style.transform = 'none'
  946. })).observe(document.body, { attributes: true, subtree: true, attributeFilter: ['style'] })
  947. document.documentElement.setAttribute('sidebar-click-zoom-observed', true)
  948. }
  949.  
  950. })()