@kudoai/chatgpt.js 3.3.5 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +29 -21
  2. package/chatgpt.js +149 -94
  3. package/dist/chatgpt.min.js +3 -3
  4. package/docs/README.md +29 -21
  5. package/docs/SECURITY.md +1 -3
  6. package/docs/USERGUIDE.md +3 -3
  7. package/package.json +18 -11
  8. package/starters/chrome/docs/README.md +10 -8
  9. package/starters/chrome/extension/components/icons.js +31 -0
  10. package/starters/chrome/extension/components/modals.js +148 -0
  11. package/starters/chrome/extension/content.js +108 -47
  12. package/starters/chrome/extension/icons/faded/icon128.png +0 -0
  13. package/starters/chrome/extension/icons/faded/icon16.png +0 -0
  14. package/starters/chrome/extension/icons/faded/icon32.png +0 -0
  15. package/starters/chrome/extension/icons/faded/icon64.png +0 -0
  16. package/starters/chrome/extension/lib/chatgpt.js +149 -94
  17. package/starters/chrome/extension/lib/dom.js +35 -0
  18. package/starters/chrome/extension/lib/settings.js +30 -0
  19. package/starters/chrome/extension/manifest.json +22 -22
  20. package/starters/chrome/extension/popup/controller.js +140 -0
  21. package/starters/chrome/extension/popup/index.html +7 -44
  22. package/starters/chrome/extension/popup/style.css +33 -10
  23. package/starters/chrome/extension/service-worker.js +41 -0
  24. package/starters/greasemonkey/chatgpt.js-greasemonkey-starter.user.js +12 -12
  25. package/starters/greasemonkey/docs/README.md +2 -0
  26. package/starters/chrome/extension/background.js +0 -14
  27. package/starters/chrome/extension/lib/settings-utils.js +0 -24
  28. package/starters/chrome/extension/popup/popup.js +0 -92
  29. package/starters/chrome/media/images/icons/refresh/icon16.png +0 -0
  30. package/starters/chrome/media/images/icons/refresh/icon50.png +0 -0
  31. /package/starters/chrome/{media/images → images}/icons/question-mark/icon16.png +0 -0
  32. /package/starters/chrome/{media/images → images}/icons/question-mark/icon512.png +0 -0
  33. /package/starters/chrome/{media/images → images}/screenshots/chatgpt-extension-in-list.png +0 -0
  34. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.png +0 -0
  35. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.psd +0 -0
  36. /package/starters/chrome/{media/images → images}/screenshots/extension-loaded.png +0 -0
  37. /package/starters/chrome/{media/images → images}/screenshots/load-unpacked-button.png +0 -0
  38. /package/starters/chrome/{media/images → images}/screenshots/reload-extension-button.png +0 -0
  39. /package/starters/chrome/{media/images → images}/screenshots/reload-page-button.png +0 -0
  40. /package/starters/chrome/{media/images → images}/screenshots/select-extension-folder.png +0 -0
package/chatgpt.js CHANGED
@@ -8,7 +8,7 @@ localStorage.alertQueue = JSON.stringify([]);
8
8
  localStorage.notifyProps = JSON.stringify({ queue: { topRight: [], bottomRight: [], bottomLeft: [], topLeft: [] }});
9
9
 
10
10
  // Define chatgpt API
11
- const chatgpt = { // eslint-disable-line no-redeclare
11
+ const chatgpt = {
12
12
  openAIaccessToken: {}, endpoints: {
13
13
  assets: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js',
14
14
  openAI: {
@@ -64,9 +64,64 @@ const chatgpt = { // eslint-disable-line no-redeclare
64
64
  // [ title/msg = strings, btns = [named functions], checkbox = named function, width (px) = int ] = optional
65
65
  // * Spaces are inserted into button labels by parsing function names in camel/kebab/snake case
66
66
 
67
+ // Init env context
67
68
  const scheme = chatgpt.isDarkMode() ? 'dark' : 'light',
68
69
  isMobile = chatgpt.browser.isMobile();
69
70
 
71
+ // Define event handlers
72
+ const handlers = {
73
+
74
+ dismiss: {
75
+ click(event) {
76
+ if (event.target == event.currentTarget || event.target.closest('[class*="-close-btn]'))
77
+ dismissAlert()
78
+ },
79
+
80
+ key(event) {
81
+ if (!/^(?: |Space|Enter|Return|Esc)/.test(event.key) || ![32, 13, 27].includes(event.keyCode))
82
+ return
83
+ for (const alertId of alertQueue) { // look to handle only if triggering alert is active
84
+ const alert = document.getElementById(alertId)
85
+ if (!alert || alert.style.display == 'none') return
86
+ if (event.key.startsWith('Esc') || event.keyCode == 27) dismissAlert() // and do nothing
87
+ else { // Space/Enter pressed
88
+ const mainButton = alert.querySelector('.modal-buttons').lastChild // look for main button
89
+ if (mainButton) { mainButton.click() ; event.preventDefault() } // click if found
90
+ }
91
+ }
92
+ }
93
+ },
94
+
95
+ drag: {
96
+ mousedown(event) { // find modal, attach listeners, init XY offsets
97
+ if (event.button != 0) return // prevent non-left-click drag
98
+ if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
99
+ chatgpt.draggableElem = event.currentTarget
100
+ chatgpt.draggableElem.style.cursor = 'grabbing'
101
+ event.preventDefault(); // prevent sub-elems like icons being draggable
102
+ ['mousemove', 'mouseup'].forEach(eventType =>
103
+ document.addEventListener(eventType, handlers.drag[eventType]))
104
+ const draggableElemRect = chatgpt.draggableElem.getBoundingClientRect()
105
+ handlers.drag.offsetX = event.clientX - draggableElemRect.left +21
106
+ handlers.drag.offsetY = event.clientY - draggableElemRect.top +12
107
+ },
108
+
109
+ mousemove(event) { // drag modal
110
+ if (!chatgpt.draggableElem) return
111
+ const newX = event.clientX - handlers.drag.offsetX,
112
+ newY = event.clientY - handlers.drag.offsetY
113
+ Object.assign(chatgpt.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
114
+ },
115
+
116
+ mouseup() { // remove listeners, reset chatgpt.draggableElem
117
+ chatgpt.draggableElem.style.cursor = 'inherit';
118
+ ['mousemove', 'mouseup'].forEach(eventType =>
119
+ document.removeEventListener(eventType, handlers.drag[eventType]))
120
+ chatgpt.draggableElem = null
121
+ }
122
+ }
123
+ }
124
+
70
125
  // Create modal parent/children elements
71
126
  const modalContainer = document.createElement('div');
72
127
  modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
@@ -76,7 +131,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
76
131
  modalMessage = document.createElement('p');
77
132
 
78
133
  // Create/append/update modal style (if missing or outdated)
79
- const thisUpdated = 20231203; // datestamp of last edit for this file's `modalStyle`
134
+ const thisUpdated = 1735475757891 // timestamp of last edit for this file's `modalStyle`
80
135
  let modalStyle = document.querySelector('#chatgpt-modal-style'); // try to select existing style
81
136
  if (!modalStyle || parseInt(modalStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
82
137
  if (!modalStyle) { // outright missing, create/id/attr/append it first
@@ -88,26 +143,32 @@ const chatgpt = { // eslint-disable-line no-redeclare
88
143
  '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
89
144
 
90
145
  // Background styles
91
- + '.chatgpt-modal {'
146
+ + '.chatgpt-modal {'
147
+ + 'pointer-events: auto ;' // override any disabling from site modals (like guest login spam)
92
148
  + 'position: fixed ; top: 0 ; left: 0 ; width: 100% ; height: 100% ;' // expand to full view-port
93
- + 'background-color: rgba(67, 70, 72, 0) ;' // init dim bg but no opacity
94
- + 'transition: background-color 0.05s ease ;' // speed to transition in show alert routine
149
+ + 'transition: background-color 0.25s ease !important ;' // speed to show bg dim
95
150
  + 'display: flex ; justify-content: center ; align-items: center ; z-index: 9999 }' // align
96
151
 
97
152
  // Alert styles
98
153
  + '.chatgpt-modal > div {'
99
- + 'opacity: 0 ; transform: translateX(-2px) translateY(5px) ; max-width: 75vw ; word-wrap: break-word ;'
100
- + 'transition: opacity 0.1s cubic-bezier(.165,.84,.44,1), transform 0.2s cubic-bezier(.165,.84,.44,1) ;'
101
- + `background-color: ${ scheme == 'dark' ? 'black' : 'white' } ;`
102
- + ( scheme != 'dark' ? 'border: 1px solid rgba(0, 0, 0, 0.3) ;' : '' )
154
+ + 'position: absolute ;' // to be click-draggable
155
+ + 'opacity: 0 ;' // to fade-in
156
+ + `border: 1px solid ${ scheme == 'dark' ? 'white' : '#b5b5b5' };`
157
+ + `color: ${ scheme == 'dark' ? 'white' : 'black' };`
158
+ + `background-color: ${ scheme == 'dark' ? 'black' : 'white' };`
159
+ + 'transform: translateX(-3px) translateY(7px) ;' // offset to move-in from
160
+ + 'transition: opacity 0.65s cubic-bezier(.165,.84,.44,1),' // for fade-ins
161
+ + 'transform 0.55s cubic-bezier(.165,.84,.44,1) ;' // for move-ins
162
+ + 'max-width: 75vw ; word-wrap: break-word ;'
103
163
  + 'padding: 20px ; margin: 12px 23px ; border-radius: 15px ; box-shadow: 0 30px 60px rgba(0, 0, 0, .12) ;'
104
164
  + ' -webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ; }'
105
165
  + '.chatgpt-modal h2 { margin-bottom: 9px }'
106
166
  + `.chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}`
107
- + '.chatgpt-modal.animated > div { opacity: 1 ; transform: translateX(0) translateY(0) }'
108
- + '@keyframes alert-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }'
109
- + '50% { opacity: 0.25 ; transform: scale(1.05) }'
110
- + '100% { opacity: 0 ; transform: scale(1.35) }}'
167
+ + '.chatgpt-modal a:hover { text-decoration: underline }'
168
+ + '.chatgpt-modal.animated > div { z-index: 13456 ; opacity: 0.98 ; transform: translateX(0) translateY(0) }'
169
+ + '@keyframes alert-zoom-fade-out {'
170
+ + '0% { opacity: 1 } 50% { opacity: 0.25 ; transform: scale(1.05) }'
171
+ + '100% { opacity: 0 ; transform: scale(1.35) }}'
111
172
 
112
173
  // Button styles
113
174
  + '.modal-buttons { display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;'
@@ -134,13 +195,13 @@ const chatgpt = { // eslint-disable-line no-redeclare
134
195
  + '.chatgpt-modal .checkbox-group label {'
135
196
  + 'font-size: .7rem ; margin: -.04rem 0 0px .3rem ;'
136
197
  + `color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}`
137
- + '.chatgpt-modal input[type="checkbox"] { transform: scale(0.7) ;'
198
+ + '.chatgpt-modal input[type=checkbox] { transform: scale(0.7) ;'
138
199
  + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' }}`
139
- + '.chatgpt-modal input[type="checkbox"]:checked {'
200
+ + '.chatgpt-modal input[type=checkbox]:checked {'
140
201
  + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
141
202
  + 'background-color: black ; position: inherit }'
142
- + '.chatgpt-modal input[type="checkbox"]:focus { outline: none ; box-shadow: none }'
143
- );
203
+ + '.chatgpt-modal input[type=checkbox]:focus { outline: none ; box-shadow: none }'
204
+ );
144
205
  }
145
206
 
146
207
  // Insert text into elements
@@ -210,7 +271,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
210
271
  const modalElems = [closeBtn, modalTitle, modalMessage, modalButtons, checkboxDiv];
211
272
  modalElems.forEach((elem) => { modal.append(elem); });
212
273
  modal.style.width = `${ width || 458 }px`;
213
- modalContainer.append(modal); document.body.append(modalContainer);
274
+ modalContainer.append(modal); document.body.append(modalContainer);
214
275
 
215
276
  // Enqueue alert
216
277
  let alertQueue = JSON.parse(localStorage.alertQueue);
@@ -221,39 +282,22 @@ const chatgpt = { // eslint-disable-line no-redeclare
221
282
  modalContainer.style.display = 'none';
222
283
  if (alertQueue.length === 1) {
223
284
  modalContainer.style.display = '';
224
- setTimeout(() => { // delay non-0 opacity's for transition fx
225
- modalContainer.style.backgroundColor = (
226
- `rgba(67, 70, 72, ${ scheme === 'dark' ? 0.62 : 0.1 })`);
227
- modalContainer.classList.add('animated'); }, 100);
285
+ setTimeout(() => { // dim bg
286
+ modal.parentNode.style.backgroundColor = `rgba(67, 70, 72, ${ scheme == 'dark' ? 0.62 : 0.33 })`
287
+ modal.parentNode.classList.add('animated')
288
+ }, 100) // delay for transition fx
228
289
  }
229
290
 
230
- // Define click/key handlers
231
- const clickHandler = event => { // explicitly defined to support removal post-dismissal
232
- if (event.target == event.currentTarget || event.target instanceof SVGPathElement) dismissAlert(); };
233
- const keyHandler = event => { // to dismiss active alert
234
- const dismissKeys = [' ', 'Spacebar', 'Enter', 'Return', 'Escape', 'Esc'],
235
- dismissKeyCodes = [32, 13, 27];
236
- if (dismissKeys.includes(event.key) || dismissKeyCodes.includes(event.keyCode)) {
237
- for (const alertId of alertQueue) { // look to handle only if triggering alert is active
238
- const alert = document.getElementById(alertId);
239
- if (alert && alert.style.display !== 'none') { // active alert found
240
- if (event.key.includes('Esc') || event.keyCode == 27) // esc pressed
241
- dismissAlert(); // dismiss alert & do nothing
242
- else if ([' ', 'Spacebar', 'Enter', 'Return'].includes(event.key) || [32, 13].includes(event.keyCode)) { // space/enter pressed
243
- const mainButton = alert.querySelector('.modal-buttons').lastChild; // look for main button
244
- if (mainButton) { mainButton.click(); event.preventDefault(); } // click if found
245
- } return;
246
- }}}};
247
-
248
- // Add listeners to dismiss alert
291
+ // Add listeners
249
292
  const dismissElems = [modalContainer, closeBtn, closeSVG, dismissBtn];
250
- dismissElems.forEach(elem => elem.onclick = clickHandler);
251
- document.addEventListener('keydown', keyHandler);
293
+ dismissElems.forEach(elem => elem.onclick = handlers.dismiss.click);
294
+ document.addEventListener('keydown', handlers.dismiss.key);
295
+ modal.onmousedown = handlers.drag.mousedown // enable click-dragging
252
296
 
253
297
  // Define alert dismisser
254
298
  const dismissAlert = () => {
255
299
  modalContainer.style.backgroundColor = 'transparent';
256
- modal.style.animation = 'alert-zoom-fade-out 0.075s ease-out';
300
+ modal.style.animation = 'alert-zoom-fade-out 0.135s ease-out';
257
301
  setTimeout(() => { // delay removal for fade-out
258
302
 
259
303
  // Remove alert
@@ -261,7 +305,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
261
305
  alertQueue = JSON.parse(localStorage.alertQueue);
262
306
  alertQueue.shift(); // + memory
263
307
  localStorage.alertQueue = JSON.stringify(alertQueue); // + storage
264
- document.removeEventListener('keydown', keyHandler); // prevent memory leaks
308
+ document.removeEventListener('keydown', handlers.dismiss.key); // prevent memory leaks
265
309
 
266
310
  // Check for pending alerts in queue
267
311
  if (alertQueue.length > 0) {
@@ -272,7 +316,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
272
316
  }, 500);
273
317
  }
274
318
 
275
- }, 50);
319
+ }, 155);
276
320
  };
277
321
 
278
322
  return modalContainer.id; // if assignment used
@@ -413,7 +457,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
413
457
  async isIdle(timeout = null) {
414
458
  const obsConfig = { childList: true, subtree: true },
415
459
  selectors = { msgDiv: 'div[data-message-author-role]',
416
- replyDiv: 'div[data-message-author-role="assistant"]' };
460
+ replyDiv: 'div[data-message-author-role=assistant]' };
417
461
 
418
462
  // Create promises
419
463
  const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
@@ -557,7 +601,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
557
601
  .replace('Copy code', '');
558
602
  msgs.push(sender + ': ' + msg);
559
603
  });
560
- transcript = msgs.join('\n\n');
604
+ transcript = msgs.join('\n\n');
561
605
 
562
606
  // ...or from getChatData(chatToGet)
563
607
  } else {
@@ -578,7 +622,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
578
622
  filename = `${ parsedHtml.querySelector('title').textContent || 'ChatGPT conversation' }.html`;
579
623
 
580
624
  // Convert relative CSS paths to absolute ones
581
- const cssLinks = parsedHtml.querySelectorAll('link[rel="stylesheet"]');
625
+ const cssLinks = parsedHtml.querySelectorAll('link[rel=stylesheet]');
582
626
  cssLinks.forEach(link => {
583
627
  const href = link.getAttribute('href');
584
628
  if (href?.startsWith('/')) link.setAttribute('href', 'https://chat.openai.com' + href);
@@ -606,7 +650,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
606
650
  } else { // auto-save to file
607
651
 
608
652
  if (format == 'md') { // remove extraneous HTML + fix file extension
609
- const mdMatch = /<.*(?:<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?)<[^/]/.exec(transcript)[1];
653
+ const mdMatch = /<.*<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?<[^/]/.exec(transcript)[1];
610
654
  transcript = mdMatch || transcript; filename = filename.replace('.html', '.md');
611
655
  }
612
656
  const blob = new Blob([transcript],
@@ -621,20 +665,20 @@ const chatgpt = { // eslint-disable-line no-redeclare
621
665
  focusChatbar() { chatgpt.getChatBox()?.focus(); },
622
666
 
623
667
  footer: {
624
- get() { return document.querySelector('main form')?.parentNode.parentNode.nextElementSibling; },
668
+ get() { return document.querySelector('.min-h-4'); },
625
669
 
626
- hide() {
670
+ hide() {
627
671
  const footer = chatgpt.footer.get();
628
672
  if (!footer) return console.error('Footer element not found!');
629
673
  if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!');
630
- footer.style.visibility = 'hidden'; footer.style.height = '3px';
674
+ footer.style.display = 'none';
631
675
  },
632
676
 
633
677
  show() {
634
678
  const footer = chatgpt.footer.get();
635
679
  if (!footer) return console.error('Footer element not found!');
636
680
  if (footer.style.visibility != 'hidden') return console.info('Footer already shown!');
637
- footer.style.visibility = footer.style.height = 'inherit';
681
+ footer.style.display = 'inherit'
638
682
  }
639
683
  },
640
684
 
@@ -888,12 +932,15 @@ const chatgpt = { // eslint-disable-line no-redeclare
888
932
  getHeaderDiv() { return chatgpt.header.get(); },
889
933
  getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest'); },
890
934
  getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
891
- getNewChatButton() { return document.querySelector('button[data-testid*="new-chat-button"]'); },
935
+
936
+ getNewChatButton() {
937
+ return document.querySelector('button[data-testid*=new-chat-button], button:has([d^="M15.6729"])'); },
938
+
892
939
  getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
893
940
  getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
894
941
 
895
942
  getResponse() {
896
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
943
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
897
944
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
898
945
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
899
946
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -904,8 +951,8 @@ const chatgpt = { // eslint-disable-line no-redeclare
904
951
  getResponseFromAPI(chatToGet, responseToGet) { return chatgpt.response.getFromAPI(chatToGet, responseToGet); },
905
952
  getResponseFromDOM(pos) { return chatgpt.response.getFromDOM(pos); },
906
953
  getScrollToBottomButton() { return document.querySelector('button:has([d^="M12 21C11.7348"])'); },
907
- getSendButton() { return document.querySelector('[data-testid="send-button"]'); },
908
- getStopButton() { return document.querySelector('button[data-testid="stop-button"]'); },
954
+ getSendButton() { return document.querySelector('[data-testid=send-button]'); },
955
+ getStopButton() { return document.querySelector('button[data-testid=stop-button]'); },
909
956
 
910
957
  getUserLanguage() {
911
958
  return navigator.languages[0] || navigator.language || navigator.browserLanguage ||
@@ -1147,7 +1194,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1147
1194
  }
1148
1195
 
1149
1196
  else if (element == 'dropdown') {
1150
- if (!attrs?.items || // there no are options to add
1197
+ if (!attrs?.items || // there no are options to add
1151
1198
  !Array.isArray(attrs.items) || // it's not an array
1152
1199
  !attrs.items.length) // the array is empty
1153
1200
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // set default dropdown entry
@@ -1166,9 +1213,9 @@ const chatgpt = { // eslint-disable-line no-redeclare
1166
1213
  }
1167
1214
 
1168
1215
  const addElementsToMenu = () => {
1169
- const optionButtons = document.querySelectorAll('a[role="menuitem"]');
1216
+ const optionButtons = document.querySelectorAll('a[role=menuitem]');
1170
1217
  let cssClasses;
1171
-
1218
+
1172
1219
  for (const navLink of optionButtons)
1173
1220
  if (navLink.textContent == 'Settings') {
1174
1221
  cssClasses = navLink.classList;
@@ -1185,7 +1232,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1185
1232
  };
1186
1233
 
1187
1234
  this.elements.push(newElement);
1188
- const menuBtn = document.querySelector('nav button[id*="headless"]');
1235
+ const menuBtn = document.querySelector('nav button[id*=headless]');
1189
1236
  if (!this.addedEvent) { // to prevent adding more than one event
1190
1237
  menuBtn?.addEventListener('click', () => { setTimeout(addElementsToMenu, 25); });
1191
1238
  this.addedEvent = true; }
@@ -1194,12 +1241,12 @@ const chatgpt = { // eslint-disable-line no-redeclare
1194
1241
  },
1195
1242
 
1196
1243
  close() {
1197
- try { document.querySelector('nav [id*="menu-button"][aria-expanded="true"]').click(); }
1244
+ try { document.querySelector('nav [id*=menu-button][aria-expanded=true]').click(); }
1198
1245
  catch (err) { console.error(err.message); }
1199
1246
  },
1200
1247
 
1201
1248
  open() {
1202
- try { document.querySelector('nav [id*="menu-button"][aria-expanded="false"]').click(); }
1249
+ try { document.querySelector('nav [id*=menu-button][aria-expanded=false]').click(); }
1203
1250
  catch (err) { console.error(err.message); }
1204
1251
  }
1205
1252
  },
@@ -1240,7 +1287,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1240
1287
  + (notificationDiv.isRight ? 'Right' : 'Left');
1241
1288
 
1242
1289
  // Create/append/update notification style (if missing or outdated)
1243
- const thisUpdated = 20231110; // datestamp of last edit for this file's `notifStyle`
1290
+ const thisUpdated = 1735475527153 // timestamp of last edit for this file's `notifStyle`
1244
1291
  let notifStyle = document.querySelector('#chatgpt-notif-style'); // try to select existing style
1245
1292
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1246
1293
  if (!notifStyle) { // outright missing, create/id/attr/append it first
@@ -1250,12 +1297,14 @@ const chatgpt = { // eslint-disable-line no-redeclare
1250
1297
  }
1251
1298
  notifStyle.innerText = ( // update prev/new style contents
1252
1299
  '.chatgpt-notif {'
1300
+ + 'font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC",'
1301
+ + '"Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif ;'
1253
1302
  + '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
1254
1303
  + 'background-color: black ; padding: 10px 13px 10px 18px ; border-radius: 11px ; border: 1px solid #f5f5f7 ;' // bubble style
1255
1304
  + 'opacity: 0 ; position: fixed ; z-index: 9999 ; font-size: 1.8rem ; color: white ;' // visibility
1256
1305
  + '-webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ;'
1257
1306
  + `transform: translateX(${ !notificationDiv.isRight ? '-' : '' }35px) ;` // init off-screen for transition fx
1258
- + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(shadow|on)\b/gi.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1307
+ + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(?:shadow|on)\b/i.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1259
1308
  + '.notif-close-btn { cursor: pointer ; float: right ; position: relative ; right: -4px ; margin-left: -3px ;'
1260
1309
  + 'display: grid }' // top-align for non-OpenAI sites
1261
1310
  + '@keyframes notif-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }' // transition out keyframes
@@ -1263,7 +1312,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1263
1312
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1264
1313
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1265
1314
  );
1266
- }
1315
+ }
1267
1316
 
1268
1317
  // Enqueue notification
1269
1318
  let notifyProps = JSON.parse(localStorage.notifyProps);
@@ -1296,7 +1345,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1296
1345
  notificationDiv.style.transition = 'transform 0.15s ease, opacity 0.15s ease';
1297
1346
  }, 10);
1298
1347
 
1299
- // Init delay before hiding
1348
+ // Init delay before hiding
1300
1349
  const hideDelay = fadeDuration > notifDuration ? 0 // don't delay if fade exceeds notification duration
1301
1350
  : notifDuration - fadeDuration; // otherwise delay for difference
1302
1351
 
@@ -1305,7 +1354,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1305
1354
  notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1306
1355
  clearTimeout(dismissFuncTID);
1307
1356
  };
1308
- const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1357
+ const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1309
1358
  closeSVG.onclick = dismissNotif; // add to close button clicks
1310
1359
 
1311
1360
  // Destroy notification
@@ -1341,8 +1390,9 @@ const chatgpt = { // eslint-disable-line no-redeclare
1341
1390
  const functionNames = [];
1342
1391
  for (const prop in this) {
1343
1392
  if (typeof this[prop] == 'function') {
1344
- const chatgptIsParent = !Object.keys(this).find(obj => Object.keys(this[obj]).includes(this[prop].name)),
1345
- functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1393
+ const chatgptIsParent = !Object.keys(this)
1394
+ .find(obj => Object.keys(this[obj]).includes(this[prop].name))
1395
+ const functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1346
1396
  functionNames.push([functionParent, prop]);
1347
1397
  } else if (typeof this[prop] == 'object') {
1348
1398
  for (const nestedProp in this[prop]) {
@@ -1398,7 +1448,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1398
1448
 
1399
1449
  renderHTML(node) {
1400
1450
  const reTags = /<([a-z\d]+)\b([^>]*)>([\s\S]*?)<\/\1>/g,
1401
- reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g,
1451
+ reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g, // eslint-disable-line
1402
1452
  nodeContent = node.childNodes;
1403
1453
 
1404
1454
  // Preserve consecutive spaces + line breaks
@@ -1453,7 +1503,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1453
1503
  continue() { try { chatgpt.getContinueBtn().click(); } catch (err) { console.error(err.message); }},
1454
1504
 
1455
1505
  get() {
1456
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1506
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1457
1507
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
1458
1508
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
1459
1509
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -1472,7 +1522,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1472
1522
  },
1473
1523
 
1474
1524
  getFromDOM(pos) {
1475
- const responseDivs = document.querySelectorAll('div[data-message-author-role="assistant"]'),
1525
+ const responseDivs = document.querySelectorAll('div[data-message-author-role=assistant]'),
1476
1526
  strPos = pos.toString().toLowerCase();
1477
1527
  let response = '';
1478
1528
  if (!responseDivs.length) return console.error('No conversation found!');
@@ -1497,8 +1547,8 @@ const chatgpt = { // eslint-disable-line no-redeclare
1497
1547
  : /^(?:10|ten)(?:th)?$/.test(strPos) ? 10 : 1 )
1498
1548
 
1499
1549
  // Transform base number if suffixed
1500
- * ( /(ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1501
- + ( /teen(th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1550
+ * ( /(?:ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1551
+ + ( /teen(?:th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1502
1552
 
1503
1553
  );
1504
1554
  response = responseDivs[nthOfResponse - 1].textContent;
@@ -1682,14 +1732,14 @@ const chatgpt = { // eslint-disable-line no-redeclare
1682
1732
  break;
1683
1733
  }
1684
1734
  }
1685
-
1735
+
1686
1736
  // Apply CSS to make the added elements look like they belong to the website
1687
1737
  this.elements.forEach(element => {
1688
1738
  element.setAttribute('class', cssClasses);
1689
1739
  element.style.maxHeight = element.style.minHeight = '44px'; // Fix the height of the element
1690
1740
  element.style.margin = '2px 0';
1691
1741
  });
1692
-
1742
+
1693
1743
  // Create MutationObserver instance
1694
1744
  const navBar = document.querySelector('nav');
1695
1745
  if (!navBar) return console.error('Sidebar element not found!');
@@ -1746,7 +1796,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1746
1796
  }
1747
1797
 
1748
1798
  else if (element == 'dropdown') {
1749
- if (!attrs?.items || // There no are options to add
1799
+ if (!attrs?.items || // There no are options to add
1750
1800
  !Array.isArray(attrs.items) || // It's not an array
1751
1801
  !attrs.items.length) // The array is empty
1752
1802
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // Set default dropdown entry
@@ -1761,7 +1811,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1761
1811
  newElement.add(optionElement);
1762
1812
  });
1763
1813
  }
1764
-
1814
+
1765
1815
 
1766
1816
  // Fix for blank background on dropdown elements
1767
1817
  if (element == 'dropdown') newElement.style.backgroundColor = 'var(--gray-900, rgb(32, 33, 35))';
@@ -1779,7 +1829,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1779
1829
  isOff() { return !this.isOn(); },
1780
1830
  isOn() {
1781
1831
  const sidebar = (() => {
1782
- return chatgpt.sidebar.exists() ? document.querySelector('[class*="sidebar"]') : null; })();
1832
+ return chatgpt.sidebar.exists() ? document.querySelector('[class*=sidebar]') : null; })();
1783
1833
  if (!sidebar) { console.error('Sidebar element not found!'); return false; }
1784
1834
  else return chatgpt.browser.isMobile() ?
1785
1835
  document.documentElement.style.overflow == 'hidden'
@@ -1787,7 +1837,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1787
1837
  },
1788
1838
 
1789
1839
  toggle() {
1790
- const sidebarToggle = document.querySelector('button[data-testid*="sidebar-button"]');
1840
+ const sidebarToggle = document.querySelector('button[data-testid*=sidebar-button]');
1791
1841
  if (!sidebarToggle) console.error('Sidebar toggle not found!');
1792
1842
  sidebarToggle.click();
1793
1843
  },
@@ -1862,7 +1912,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1862
1912
  if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
1863
1913
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1864
1914
  return console.error(`Argument ${ i + 1 } must be a string!`);
1865
- chatgpt.send('Translate the following text to ' + outputLang
1915
+ chatgpt.send('Translate the following text to ' + outputLang
1866
1916
  + '. Only reply with the translation.\n\n' + text);
1867
1917
  console.info('Translating text...');
1868
1918
  await chatgpt.isIdle();
@@ -1872,14 +1922,19 @@ const chatgpt = { // eslint-disable-line no-redeclare
1872
1922
  unminify() { chatgpt.code.unminify(); },
1873
1923
 
1874
1924
  uuidv4() {
1875
- let d = new Date().getTime(); // get current timestamp in ms (to ensure UUID uniqueness)
1876
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1877
- const r = ( // generate random nibble
1878
- ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 );
1879
- d = Math.floor(d/16); // correspond each UUID digit to unique 4-bit chunks of timestamp
1880
- return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16); // generate random hexadecimal digit
1881
- });
1882
- return uuid;
1925
+ try {
1926
+ // use native secure uuid generator when available
1927
+ return crypto.randomUUID();
1928
+ } catch(_e) {
1929
+ let d = new Date().getTime(); // get current timestamp in ms (to ensure UUID uniqueness)
1930
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1931
+ const r = ( // generate random nibble
1932
+ ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 );
1933
+ d = Math.floor(d/16); // correspond each UUID digit to unique 4-bit chunks of timestamp
1934
+ return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16); // generate random hexadecimal digit
1935
+ });
1936
+ return uuid;
1937
+ }
1883
1938
  },
1884
1939
 
1885
1940
  writeCode() { chatgpt.code.write(); }
@@ -1892,8 +1947,8 @@ const cjsBtnActions = ['click', 'get'], cjsTargetTypes = [ 'button', 'link', 'di
1892
1947
  for (const btnAction of cjsBtnActions) {
1893
1948
  chatgpt[btnAction + 'Button'] = function handleButton(buttonIdentifier) {
1894
1949
  const button = /^[.#]/.test(buttonIdentifier) ? document.querySelector(buttonIdentifier)
1895
- : /send/i.test(buttonIdentifier) ? document.querySelector('form button[class*="bottom"]')
1896
- : /scroll/i.test(buttonIdentifier) ? document.querySelector('button[class*="cursor"]')
1950
+ : /send/i.test(buttonIdentifier) ? document.querySelector('form button[class*=bottom]')
1951
+ : /scroll/i.test(buttonIdentifier) ? document.querySelector('button[class*=cursor]')
1897
1952
  : (function() { // get via text content
1898
1953
  for (const button of document.querySelectorAll('button')) { // try buttons
1899
1954
  if (button.textContent.toLowerCase().includes(buttonIdentifier.toLowerCase())) {
@@ -2011,7 +2066,7 @@ for (const prop in chatgpt) {
2011
2066
  // Prefix console logs w/ '🤖 chatgpt.js >> '
2012
2067
  const consolePrefix = '🤖 chatgpt.js >> ', ogError = console.error, ogInfo = console.info;
2013
2068
  console.error = (...args) => {
2014
- if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2069
+ if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2015
2070
  else ogError(...args);
2016
2071
  };
2017
2072
  console.info = (msg) => {