@kudoai/chatgpt.js 3.3.4 → 3.4.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 (41) hide show
  1. package/README.md +35 -22
  2. package/chatgpt.js +130 -79
  3. package/dist/chatgpt.min.js +3 -3
  4. package/docs/README.md +35 -22
  5. package/docs/SECURITY.md +6 -1
  6. package/docs/USERGUIDE.md +4 -4
  7. package/package.json +18 -11
  8. package/starters/chrome/docs/README.md +13 -9
  9. package/starters/chrome/extension/components/icons.js +32 -0
  10. package/starters/chrome/extension/components/modals.js +121 -0
  11. package/starters/chrome/extension/content.js +121 -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 +130 -79
  17. package/starters/chrome/extension/lib/dom.js +28 -0
  18. package/starters/chrome/extension/lib/settings.js +30 -0
  19. package/starters/chrome/extension/manifest.json +25 -22
  20. package/starters/chrome/extension/popup/controller.js +138 -0
  21. package/starters/chrome/extension/popup/index.html +7 -44
  22. package/starters/chrome/extension/popup/style.css +15 -6
  23. package/starters/chrome/extension/service-worker.js +38 -0
  24. package/starters/docs/README.md +9 -2
  25. package/starters/greasemonkey/chatgpt.js-greasemonkey-starter.user.js +12 -12
  26. package/starters/greasemonkey/docs/README.md +2 -0
  27. package/starters/chrome/extension/background.js +0 -14
  28. package/starters/chrome/extension/lib/settings-utils.js +0 -24
  29. package/starters/chrome/extension/popup/popup.js +0 -92
  30. package/starters/chrome/media/images/icons/refresh/icon16.png +0 -0
  31. package/starters/chrome/media/images/icons/refresh/icon50.png +0 -0
  32. /package/starters/chrome/{media/images → images}/icons/question-mark/icon16.png +0 -0
  33. /package/starters/chrome/{media/images → images}/icons/question-mark/icon512.png +0 -0
  34. /package/starters/chrome/{media/images → images}/screenshots/chatgpt-extension-in-list.png +0 -0
  35. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.png +0 -0
  36. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.psd +0 -0
  37. /package/starters/chrome/{media/images → images}/screenshots/extension-loaded.png +0 -0
  38. /package/starters/chrome/{media/images → images}/screenshots/load-unpacked-button.png +0 -0
  39. /package/starters/chrome/{media/images → images}/screenshots/reload-extension-button.png +0 -0
  40. /package/starters/chrome/{media/images → images}/screenshots/reload-page-button.png +0 -0
  41. /package/starters/chrome/{media/images → images}/screenshots/select-extension-folder.png +0 -0
@@ -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,63 @@ 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(event => document.addEventListener(event, handlers.drag[event]))
103
+ const draggableElemRect = chatgpt.draggableElem.getBoundingClientRect()
104
+ handlers.drag.offsetX = event.clientX - draggableElemRect.left +21
105
+ handlers.drag.offsetY = event.clientY - draggableElemRect.top +12
106
+ },
107
+
108
+ mousemove(event) { // drag modal
109
+ if (!chatgpt.draggableElem) return
110
+ const newX = event.clientX - handlers.drag.offsetX,
111
+ newY = event.clientY - handlers.drag.offsetY
112
+ Object.assign(chatgpt.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
113
+ },
114
+
115
+ mouseup() { // remove listeners, reset chatgpt.draggableElem
116
+ chatgpt.draggableElem.style.cursor = 'inherit';
117
+ ['mousemove', 'mouseup'].forEach(event =>
118
+ document.removeEventListener(event, handlers.drag[event]))
119
+ chatgpt.draggableElem = null
120
+ }
121
+ }
122
+ }
123
+
70
124
  // Create modal parent/children elements
71
125
  const modalContainer = document.createElement('div');
72
126
  modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
@@ -76,7 +130,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
76
130
  modalMessage = document.createElement('p');
77
131
 
78
132
  // Create/append/update modal style (if missing or outdated)
79
- const thisUpdated = 20231203; // datestamp of last edit for this file's `modalStyle`
133
+ const thisUpdated = 1734685032942; // timestamp of last edit for this file's `modalStyle`
80
134
  let modalStyle = document.querySelector('#chatgpt-modal-style'); // try to select existing style
81
135
  if (!modalStyle || parseInt(modalStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
82
136
  if (!modalStyle) { // outright missing, create/id/attr/append it first
@@ -88,26 +142,31 @@ const chatgpt = { // eslint-disable-line no-redeclare
88
142
  '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
89
143
 
90
144
  // Background styles
91
- + '.chatgpt-modal {'
145
+ + '.chatgpt-modal {'
146
+ + 'pointer-events: auto ;' // override any disabling from site modals (like guest login spam)
92
147
  + '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
148
+ + 'transition: background-color 0.25s ease !important ;' // speed to show bg dim
95
149
  + 'display: flex ; justify-content: center ; align-items: center ; z-index: 9999 }' // align
96
150
 
97
151
  // Alert styles
98
152
  + '.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) ;' : '' )
153
+ + 'position: absolute ;' // to be click-draggable
154
+ + 'opacity: 0 ;' // to fade-in
155
+ + `border: 1px solid ${ scheme == 'dark' ? 'white' : '#b5b5b5' };`
156
+ + `color: ${ scheme == 'dark' ? 'white' : 'black' };`
157
+ + `background-color: ${ scheme == 'dark' ? 'black' : 'white' };`
158
+ + 'transform: translateX(-3px) translateY(7px) ;' // offset to move-in from
159
+ + 'transition: opacity 0.65s cubic-bezier(.165,.84,.44,1),' // for fade-ins
160
+ + 'transform 0.55s cubic-bezier(.165,.84,.44,1) ;' // for move-ins
161
+ + 'max-width: 75vw ; word-wrap: break-word ;'
103
162
  + 'padding: 20px ; margin: 12px 23px ; border-radius: 15px ; box-shadow: 0 30px 60px rgba(0, 0, 0, .12) ;'
104
163
  + ' -webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ; }'
105
164
  + '.chatgpt-modal h2 { margin-bottom: 9px }'
106
165
  + `.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) }}'
166
+ + '.chatgpt-modal.animated > div { z-index: 13456 ; opacity: 0.98 ; transform: translateX(0) translateY(0) }'
167
+ + '@keyframes alert-zoom-fade-out {'
168
+ + '0% { opacity: 1 } 50% { opacity: 0.25 ; transform: scale(1.05) }'
169
+ + '100% { opacity: 0 ; transform: scale(1.35) }}'
111
170
 
112
171
  // Button styles
113
172
  + '.modal-buttons { display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;'
@@ -140,7 +199,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
140
199
  + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
141
200
  + 'background-color: black ; position: inherit }'
142
201
  + '.chatgpt-modal input[type="checkbox"]:focus { outline: none ; box-shadow: none }'
143
- );
202
+ );
144
203
  }
145
204
 
146
205
  // Insert text into elements
@@ -210,7 +269,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
210
269
  const modalElems = [closeBtn, modalTitle, modalMessage, modalButtons, checkboxDiv];
211
270
  modalElems.forEach((elem) => { modal.append(elem); });
212
271
  modal.style.width = `${ width || 458 }px`;
213
- modalContainer.append(modal); document.body.append(modalContainer);
272
+ modalContainer.append(modal); document.body.append(modalContainer);
214
273
 
215
274
  // Enqueue alert
216
275
  let alertQueue = JSON.parse(localStorage.alertQueue);
@@ -221,39 +280,22 @@ const chatgpt = { // eslint-disable-line no-redeclare
221
280
  modalContainer.style.display = 'none';
222
281
  if (alertQueue.length === 1) {
223
282
  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);
283
+ setTimeout(() => { // dim bg
284
+ modal.parentNode.style.backgroundColor = `rgba(67, 70, 72, ${ scheme == 'dark' ? 0.62 : 0.33 })`
285
+ modal.parentNode.classList.add('animated')
286
+ }, 100) // delay for transition fx
228
287
  }
229
288
 
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
289
+ // Add listeners
249
290
  const dismissElems = [modalContainer, closeBtn, closeSVG, dismissBtn];
250
- dismissElems.forEach(elem => elem.onclick = clickHandler);
251
- document.addEventListener('keydown', keyHandler);
291
+ dismissElems.forEach(elem => elem.onclick = handlers.dismiss.click);
292
+ document.addEventListener('keydown', handlers.dismiss.key);
293
+ modal.onmousedown = handlers.drag.mousedown // enable click-dragging
252
294
 
253
295
  // Define alert dismisser
254
296
  const dismissAlert = () => {
255
297
  modalContainer.style.backgroundColor = 'transparent';
256
- modal.style.animation = 'alert-zoom-fade-out 0.075s ease-out';
298
+ modal.style.animation = 'alert-zoom-fade-out 0.135s ease-out';
257
299
  setTimeout(() => { // delay removal for fade-out
258
300
 
259
301
  // Remove alert
@@ -261,7 +303,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
261
303
  alertQueue = JSON.parse(localStorage.alertQueue);
262
304
  alertQueue.shift(); // + memory
263
305
  localStorage.alertQueue = JSON.stringify(alertQueue); // + storage
264
- document.removeEventListener('keydown', keyHandler); // prevent memory leaks
306
+ document.removeEventListener('keydown', handlers.dismiss.key); // prevent memory leaks
265
307
 
266
308
  // Check for pending alerts in queue
267
309
  if (alertQueue.length > 0) {
@@ -272,7 +314,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
272
314
  }, 500);
273
315
  }
274
316
 
275
- }, 50);
317
+ }, 135);
276
318
  };
277
319
 
278
320
  return modalContainer.id; // if assignment used
@@ -557,7 +599,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
557
599
  .replace('Copy code', '');
558
600
  msgs.push(sender + ': ' + msg);
559
601
  });
560
- transcript = msgs.join('\n\n');
602
+ transcript = msgs.join('\n\n');
561
603
 
562
604
  // ...or from getChatData(chatToGet)
563
605
  } else {
@@ -606,7 +648,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
606
648
  } else { // auto-save to file
607
649
 
608
650
  if (format == 'md') { // remove extraneous HTML + fix file extension
609
- const mdMatch = /<.*(?:<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?)<[^/]/.exec(transcript)[1];
651
+ const mdMatch = /<.*<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?<[^/]/.exec(transcript)[1];
610
652
  transcript = mdMatch || transcript; filename = filename.replace('.html', '.md');
611
653
  }
612
654
  const blob = new Blob([transcript],
@@ -621,20 +663,20 @@ const chatgpt = { // eslint-disable-line no-redeclare
621
663
  focusChatbar() { chatgpt.getChatBox()?.focus(); },
622
664
 
623
665
  footer: {
624
- get() { return document.querySelector('main form')?.parentNode.parentNode.nextElementSibling; },
666
+ get() { return document.querySelector('.min-h-4'); },
625
667
 
626
- hide() {
668
+ hide() {
627
669
  const footer = chatgpt.footer.get();
628
670
  if (!footer) return console.error('Footer element not found!');
629
671
  if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!');
630
- footer.style.visibility = 'hidden'; footer.style.height = '3px';
672
+ footer.style.display = 'none';
631
673
  },
632
674
 
633
675
  show() {
634
676
  const footer = chatgpt.footer.get();
635
677
  if (!footer) return console.error('Footer element not found!');
636
678
  if (footer.style.visibility != 'hidden') return console.info('Footer already shown!');
637
- footer.style.visibility = footer.style.height = 'inherit';
679
+ footer.style.display = 'inherit'
638
680
  }
639
681
  },
640
682
 
@@ -883,17 +925,20 @@ const chatgpt = { // eslint-disable-line no-redeclare
883
925
  },
884
926
 
885
927
  getChatInput() { return chatgpt.getChatBox().firstChild.innerText; },
886
- getContinueButton() { return document.querySelector('button:has([d^="M4.47189"])'); },
928
+ getContinueButton() { return document.querySelector('button.btn:has([d^="M4.47189"])'); },
887
929
  getFooterDiv() { return chatgpt.footer.get(); },
888
930
  getHeaderDiv() { return chatgpt.header.get(); },
889
931
  getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest'); },
890
932
  getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
891
- getNewChatButton() { return document.querySelector('button[data-testid*="new-chat-button"]'); },
933
+
934
+ getNewChatButton() {
935
+ return document.querySelector('button[data-testid*="new-chat-button"], button:has([d^="M15.6729"])'); },
936
+
892
937
  getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
893
938
  getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
894
939
 
895
940
  getResponse() {
896
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
941
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
897
942
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
898
943
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
899
944
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -1147,7 +1192,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1147
1192
  }
1148
1193
 
1149
1194
  else if (element == 'dropdown') {
1150
- if (!attrs?.items || // there no are options to add
1195
+ if (!attrs?.items || // there no are options to add
1151
1196
  !Array.isArray(attrs.items) || // it's not an array
1152
1197
  !attrs.items.length) // the array is empty
1153
1198
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // set default dropdown entry
@@ -1168,7 +1213,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1168
1213
  const addElementsToMenu = () => {
1169
1214
  const optionButtons = document.querySelectorAll('a[role="menuitem"]');
1170
1215
  let cssClasses;
1171
-
1216
+
1172
1217
  for (const navLink of optionButtons)
1173
1218
  if (navLink.textContent == 'Settings') {
1174
1219
  cssClasses = navLink.classList;
@@ -1240,7 +1285,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1240
1285
  + (notificationDiv.isRight ? 'Right' : 'Left');
1241
1286
 
1242
1287
  // Create/append/update notification style (if missing or outdated)
1243
- const thisUpdated = 20231110; // datestamp of last edit for this file's `notifStyle`
1288
+ const thisUpdated = 20231110; // datestamp of last edit for this file's `notifStyle`
1244
1289
  let notifStyle = document.querySelector('#chatgpt-notif-style'); // try to select existing style
1245
1290
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1246
1291
  if (!notifStyle) { // outright missing, create/id/attr/append it first
@@ -1255,7 +1300,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1255
1300
  + 'opacity: 0 ; position: fixed ; z-index: 9999 ; font-size: 1.8rem ; color: white ;' // visibility
1256
1301
  + '-webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ;'
1257
1302
  + `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 )) : '' ) + '}'
1303
+ + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(?:shadow|on)\b/i.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1259
1304
  + '.notif-close-btn { cursor: pointer ; float: right ; position: relative ; right: -4px ; margin-left: -3px ;'
1260
1305
  + 'display: grid }' // top-align for non-OpenAI sites
1261
1306
  + '@keyframes notif-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }' // transition out keyframes
@@ -1263,7 +1308,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1263
1308
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1264
1309
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1265
1310
  );
1266
- }
1311
+ }
1267
1312
 
1268
1313
  // Enqueue notification
1269
1314
  let notifyProps = JSON.parse(localStorage.notifyProps);
@@ -1296,7 +1341,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1296
1341
  notificationDiv.style.transition = 'transform 0.15s ease, opacity 0.15s ease';
1297
1342
  }, 10);
1298
1343
 
1299
- // Init delay before hiding
1344
+ // Init delay before hiding
1300
1345
  const hideDelay = fadeDuration > notifDuration ? 0 // don't delay if fade exceeds notification duration
1301
1346
  : notifDuration - fadeDuration; // otherwise delay for difference
1302
1347
 
@@ -1305,7 +1350,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1305
1350
  notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1306
1351
  clearTimeout(dismissFuncTID);
1307
1352
  };
1308
- const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1353
+ const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1309
1354
  closeSVG.onclick = dismissNotif; // add to close button clicks
1310
1355
 
1311
1356
  // Destroy notification
@@ -1341,8 +1386,9 @@ const chatgpt = { // eslint-disable-line no-redeclare
1341
1386
  const functionNames = [];
1342
1387
  for (const prop in this) {
1343
1388
  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';
1389
+ const chatgptIsParent = !Object.keys(this)
1390
+ .find(obj => Object.keys(this[obj]).includes(this[prop].name))
1391
+ const functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1346
1392
  functionNames.push([functionParent, prop]);
1347
1393
  } else if (typeof this[prop] == 'object') {
1348
1394
  for (const nestedProp in this[prop]) {
@@ -1398,7 +1444,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1398
1444
 
1399
1445
  renderHTML(node) {
1400
1446
  const reTags = /<([a-z\d]+)\b([^>]*)>([\s\S]*?)<\/\1>/g,
1401
- reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g,
1447
+ reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g, // eslint-disable-line
1402
1448
  nodeContent = node.childNodes;
1403
1449
 
1404
1450
  // Preserve consecutive spaces + line breaks
@@ -1453,7 +1499,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1453
1499
  continue() { try { chatgpt.getContinueBtn().click(); } catch (err) { console.error(err.message); }},
1454
1500
 
1455
1501
  get() {
1456
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1502
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1457
1503
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
1458
1504
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
1459
1505
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -1497,8 +1543,8 @@ const chatgpt = { // eslint-disable-line no-redeclare
1497
1543
  : /^(?:10|ten)(?:th)?$/.test(strPos) ? 10 : 1 )
1498
1544
 
1499
1545
  // 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
1546
+ * ( /(?:ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1547
+ + ( /teen(?:th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1502
1548
 
1503
1549
  );
1504
1550
  response = responseDivs[nthOfResponse - 1].textContent;
@@ -1682,14 +1728,14 @@ const chatgpt = { // eslint-disable-line no-redeclare
1682
1728
  break;
1683
1729
  }
1684
1730
  }
1685
-
1731
+
1686
1732
  // Apply CSS to make the added elements look like they belong to the website
1687
1733
  this.elements.forEach(element => {
1688
1734
  element.setAttribute('class', cssClasses);
1689
1735
  element.style.maxHeight = element.style.minHeight = '44px'; // Fix the height of the element
1690
1736
  element.style.margin = '2px 0';
1691
1737
  });
1692
-
1738
+
1693
1739
  // Create MutationObserver instance
1694
1740
  const navBar = document.querySelector('nav');
1695
1741
  if (!navBar) return console.error('Sidebar element not found!');
@@ -1746,7 +1792,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1746
1792
  }
1747
1793
 
1748
1794
  else if (element == 'dropdown') {
1749
- if (!attrs?.items || // There no are options to add
1795
+ if (!attrs?.items || // There no are options to add
1750
1796
  !Array.isArray(attrs.items) || // It's not an array
1751
1797
  !attrs.items.length) // The array is empty
1752
1798
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // Set default dropdown entry
@@ -1761,7 +1807,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1761
1807
  newElement.add(optionElement);
1762
1808
  });
1763
1809
  }
1764
-
1810
+
1765
1811
 
1766
1812
  // Fix for blank background on dropdown elements
1767
1813
  if (element == 'dropdown') newElement.style.backgroundColor = 'var(--gray-900, rgb(32, 33, 35))';
@@ -1862,7 +1908,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1862
1908
  if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
1863
1909
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1864
1910
  return console.error(`Argument ${ i + 1 } must be a string!`);
1865
- chatgpt.send('Translate the following text to ' + outputLang
1911
+ chatgpt.send('Translate the following text to ' + outputLang
1866
1912
  + '. Only reply with the translation.\n\n' + text);
1867
1913
  console.info('Translating text...');
1868
1914
  await chatgpt.isIdle();
@@ -1872,14 +1918,19 @@ const chatgpt = { // eslint-disable-line no-redeclare
1872
1918
  unminify() { chatgpt.code.unminify(); },
1873
1919
 
1874
1920
  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;
1921
+ try {
1922
+ // use native secure uuid generator when available
1923
+ return crypto.randomUUID();
1924
+ } catch(_e) {
1925
+ let d = new Date().getTime(); // get current timestamp in ms (to ensure UUID uniqueness)
1926
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1927
+ const r = ( // generate random nibble
1928
+ ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 );
1929
+ d = Math.floor(d/16); // correspond each UUID digit to unique 4-bit chunks of timestamp
1930
+ return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16); // generate random hexadecimal digit
1931
+ });
1932
+ return uuid;
1933
+ }
1883
1934
  },
1884
1935
 
1885
1936
  writeCode() { chatgpt.code.write(); }
@@ -2011,7 +2062,7 @@ for (const prop in chatgpt) {
2011
2062
  // Prefix console logs w/ '🤖 chatgpt.js >> '
2012
2063
  const consolePrefix = '🤖 chatgpt.js >> ', ogError = console.error, ogInfo = console.info;
2013
2064
  console.error = (...args) => {
2014
- if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2065
+ if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2015
2066
  else ogError(...args);
2016
2067
  };
2017
2068
  console.info = (msg) => {
@@ -0,0 +1,28 @@
1
+ window.dom = {
2
+ create: {
3
+ elem(elemType, attrs = {}) {
4
+ const elem = document.createElement(elemType)
5
+ for (const attr in attrs) elem.setAttribute(attr, attrs[attr])
6
+ return elem
7
+ },
8
+
9
+ svgElem(type, attrs) {
10
+ const elem = document.createElementNS('http://www.w3.org/2000/svg', type)
11
+ for (const attr in attrs) elem.setAttributeNS(null, attr, attrs[attr])
12
+ return elem
13
+ }
14
+ },
15
+
16
+ fillStarryBG(targetNode) { // requires https://assets.aiwebextensions.com/styles/css/<black|white>-rising-stars.min.css
17
+ const starsDivsContainer = document.createElement('div')
18
+ starsDivsContainer.style.cssText = 'position: absolute ; top: 0 ; left: 0 ;' // hug targetNode's top-left corner
19
+ + 'height: 100% ; width: 100% ; border-radius: 15px ; overflow: clip ;' // bound innards exactly by targetNode
20
+ + 'z-index: -1'; // allow interactive elems to be clicked
21
+ ['sm', 'med', 'lg'].forEach(starSize => {
22
+ const starsDiv = document.createElement('div')
23
+ starsDiv.id = `${ chatgpt.isDarkMode() ? 'white' : 'black' }-stars-${starSize}`
24
+ starsDivsContainer.append(starsDiv)
25
+ })
26
+ targetNode.prepend(starsDivsContainer)
27
+ }
28
+ };
@@ -0,0 +1,30 @@
1
+ window.config = {}
2
+ window.settings = {
3
+
4
+ // Init SETTINGS props (for popup menu)
5
+ controls: {
6
+ // Add settings options as keys, with each key's value being an object that includes:
7
+ // - 'type': the control type (e.g. 'toggle' or 'prompt')
8
+ // - 'label': a descriptive label
9
+ // - 'symbol' (optional): for icon display (e.g. ⌚)
10
+ // NOTE: Toggles are disabled by default unless key name contains 'disabled' or 'hidden' (case insensitive)
11
+ // NOTE: Controls are displayed in top-to-bottom order
12
+ // EXAMPLES:
13
+ // autoScrollDisabled: { type: 'toggle', label: 'Auto-Scroll' },
14
+ // replyLanguage: { type: 'prompt', symbol: '🌐', label: 'Reply Language' }
15
+ },
16
+
17
+ load() {
18
+ const keys = ( // original array if array, else new array from multiple args
19
+ Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments))
20
+ return Promise.all(keys.map(key => // resolve promise when all keys load
21
+ new Promise(resolve => // resolve promise when single key value loads
22
+ chrome.storage.sync.get(key, result => { // load from Chrome extension storage
23
+ window.config[key] = result[key] || false ; resolve()
24
+ }))))},
25
+
26
+ save(key, val) {
27
+ chrome.storage.sync.set({ [key]: val }) // save to Chrome extension storage
28
+ window.config[key] = val // save to memory
29
+ }
30
+ }
@@ -1,24 +1,27 @@
1
1
  {
2
- "manifest_version": 3,
3
- "name": "ChatGPT Extension",
4
- "description": "A Chrome template to start using chatgpt.js like a boss!",
5
- "version": "2024.10.3",
6
- "author": "chatgpt.js",
7
- "icons": {
8
- "16": "icons/icon16.png",
9
- "32": "icons/icon32.png",
10
- "64": "icons/icon64.png",
11
- "128": "icons/icon128.png"
12
- },
13
- "permissions": [ "storage", "tabs" ],
14
- "action": { "default_popup": "popup/index.html" },
15
- "web_accessible_resources": [{
16
- "matches": ["<all_urls>"],
17
- "resources": ["lib/settings-utils.js", "lib/chatgpt.js"]
18
- }],
19
- "content_scripts": [{
20
- "matches": ["https://chatgpt.com/*", "https://chat.openai.com/*"],
21
- "js": ["content.js"]
22
- }],
23
- "background": { "service_worker": "background.js" }
2
+ "manifest_version": 3,
3
+ "name": "ChatGPT Extension",
4
+ "short_name": "ChatGPT 🧩",
5
+ "description": "A Chromium extension template to start using chatgpt.js like a boss!",
6
+ "version": "2024.12.20",
7
+ "author": "KudoAI",
8
+ "homepage_url": "https://github.com/KudoAI/chatgpt.js-chrome-starter",
9
+ "icons": {
10
+ "16": "icons/icon16.png",
11
+ "32": "icons/icon32.png",
12
+ "64": "icons/icon64.png",
13
+ "128": "icons/icon128.png"
14
+ },
15
+ "permissions": [ "activeTab", "storage" ],
16
+ "action": { "default_popup": "popup/index.html" },
17
+ "web_accessible_resources": [{
18
+ "matches": [ "<all_urls>" ],
19
+ "resources": [ "components/modals.js", "lib/chatgpt.js", "lib/dom.js", "lib/settings.js" ]
20
+ }],
21
+ "content_scripts": [{
22
+ "matches": [ "https://chatgpt.com/*" ],
23
+ "js": [ "content.js" ]
24
+ }],
25
+ "background": { "service_worker": "service-worker.js" },
26
+ "minimum_chrome_version": "88"
24
27
  }