@kudoai/chatgpt.js 3.6.3 → 3.7.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.
package/chatgpt.js CHANGED
@@ -4,60 +4,83 @@
4
4
  // Latest minified release: https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js/chatgpt.min.js
5
5
 
6
6
  // Init feedback props
7
- localStorage.alertQueue = JSON.stringify([]);
8
- localStorage.notifyProps = JSON.stringify({ queue: { topRight: [], bottomRight: [], bottomLeft: [], topLeft: [] }});
7
+ localStorage.alertQueue = JSON.stringify([])
8
+ localStorage.notifyProps = JSON.stringify({ queue: { topRight: [], bottomRight: [], bottomLeft: [], topLeft: [] }})
9
9
 
10
10
  // Define chatgpt API
11
11
  const chatgpt = {
12
- openAIaccessToken: {}, endpoints: {
13
- assets: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js',
14
- openAI: {
15
- session: 'https://chatgpt.com/api/auth/session',
16
- chats: 'https://chatgpt.com/backend-api/conversations',
17
- chat: 'https://chatgpt.com/backend-api/conversation',
18
- share_create: 'https://chatgpt.com/backend-api/share/create',
19
- share: 'https://chatgpt.com/backend-api/share',
20
- instructions: 'https://chatgpt.com/backend-api/user_system_messages'
21
- }},
12
+
13
+ endpoints: {
14
+ assets: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js',
15
+ openAI: {
16
+ session: 'https://chatgpt.com/api/auth/session',
17
+ chats: 'https://chatgpt.com/backend-api/conversations',
18
+ chat: 'https://chatgpt.com/backend-api/conversation',
19
+ share_create: 'https://chatgpt.com/backend-api/share/create',
20
+ share: 'https://chatgpt.com/backend-api/share',
21
+ instructions: 'https://chatgpt.com/backend-api/user_system_messages'
22
+ }
23
+ },
24
+
25
+ selectors: {
26
+ btns: {
27
+ continue: 'button.btn:has([d^="M4.47189"])', login: '[data-testid*=login]',
28
+ newChat: 'button[data-testid*=new-chat-button],' // sidebar button (when logged in)
29
+ + 'button:has([d^="M3.06957"]),' // Cycle Arrows icon (Temp chat mode)
30
+ + 'button:has([d^="M15.6729"])', // Pencil icon (recorded chat mode)
31
+ regen: 'button:has([d^="M3.06957"])', scroll: 'button:has([d^="M12 21C11.7348"])',
32
+ send: '[data-testid=send-button]', sidebar: 'button[data-testid*=sidebar-button]',
33
+ stop: 'button[data-testid=stop-button]', voice: 'button[data-testid*=composer-speech-button]'
34
+ },
35
+ chatDivs: {
36
+ convo: 'main > div > div > div > div > div > div[class*=group]',
37
+ msg: 'div[data-message-author-role]', reply: 'div[data-message-author-role=assistant]'
38
+ },
39
+ chatHistory: 'nav',
40
+ errors: { txt: '[class*=text-error]' },
41
+ footer: '.min-h-4',
42
+ header: 'main .sticky',
43
+ links: { newChat: 'nav a[href="/"]', sidebarItem: 'nav a' },
44
+ sidebar: 'div[class*=sidebar]',
45
+ ssgManifest: 'script[src*="_ssgManifest.js"]'
46
+ },
22
47
 
23
48
  actAs(persona) {
24
49
  // Prompts ChatGPT to act as a persona from https://github.com/KudoAI/chat-prompts/blob/main/personas.json
25
50
 
26
- const promptsUrl = 'https://cdn.jsdelivr.net/gh/KudoAI/chat-prompts/dist/personas.min.json';
51
+ const promptsUrl = 'https://cdn.jsdelivr.net/gh/KudoAI/chat-prompts/dist/personas.min.json'
27
52
  return new Promise((resolve, reject) => {
28
- const xhr = new XMLHttpRequest();
29
- xhr.open('GET', promptsUrl, true); xhr.send();
53
+ const xhr = new XMLHttpRequest()
54
+ xhr.open('GET', promptsUrl, true) ; xhr.send()
30
55
  xhr.onload = () => {
31
- if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve prompts data.');
32
- const data = JSON.parse(xhr.responseText).personas;
56
+ if (xhr.status != 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve prompts data.')
57
+ const data = JSON.parse(xhr.responseText).personas
33
58
  if (!persona) {
34
59
  console.log('\n%c🤖 chatgpt.js personas\n',
35
- 'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold');
60
+ 'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold')
36
61
  for (const prompt of data) // list personas
37
- console.log(`%c${ prompt.title }`, 'font-family: monospace ; font-size: larger ;');
38
- return resolve();
62
+ console.log(`%c${ prompt.title }`, 'font-family: monospace ; font-size: larger ;')
63
+ return resolve()
39
64
  }
40
- const selectedPrompt = data.find(obj => obj.title.toLowerCase() == persona.toLowerCase());
65
+ const selectedPrompt = data.find(obj => obj.title.toLowerCase() == persona.toLowerCase())
41
66
  if (!selectedPrompt)
42
- return reject(`🤖 chatgpt.js >> Persona '${ persona }' was not found!`);
43
- chatgpt.send(selectedPrompt.prompt, 'click');
44
- console.info(`Loading ${ persona } persona...`);
45
- chatgpt.isIdle().then(() => { console.info('Persona activated!'); });
46
- return resolve();
47
- };
48
- });
67
+ return reject(`🤖 chatgpt.js >> Persona '${ persona }' was not found!`)
68
+ chatgpt.send(selectedPrompt.prompt, 'click')
69
+ console.info(`Loading ${ persona } persona...`)
70
+ chatgpt.isIdle().then(() => console.info('Persona activated!'))
71
+ return resolve()
72
+ }
73
+ })
49
74
  },
50
75
 
51
76
  activateDarkMode() {
52
- document.documentElement.classList.replace('light', 'dark');
53
- document.documentElement.style.colorScheme = 'dark';
54
- localStorage.setItem('theme', 'dark');
77
+ document.documentElement.classList.replace('light', 'dark')
78
+ document.documentElement.style.colorScheme = localStorage.theme = 'dark'
55
79
  },
56
80
 
57
81
  activateLightMode() {
58
- document.documentElement.classList.replace('dark', 'light');
59
- document.documentElement.style.colorScheme = 'light';
60
- localStorage.setItem('theme', 'light');
82
+ document.documentElement.classList.replace('dark', 'light')
83
+ document.documentElement.style.colorScheme = localStorage.theme = 'light'
61
84
  },
62
85
 
63
86
  alert(title, msg, btns, checkbox, width) {
@@ -66,7 +89,7 @@ const chatgpt = {
66
89
 
67
90
  // Init env context
68
91
  const scheme = chatgpt.isDarkMode() ? 'dark' : 'light',
69
- isMobile = chatgpt.browser.isMobile();
92
+ isMobile = chatgpt.browser.isMobile()
70
93
 
71
94
  // Define event handlers
72
95
  const handlers = {
@@ -85,52 +108,56 @@ const chatgpt = {
85
108
  if (!alert || alert.style.display == 'none') return
86
109
  if (event.key.startsWith('Esc') || event.keyCode == 27) dismissAlert() // and do nothing
87
110
  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
111
+ const mainBtn = alert.querySelector('.modal-buttons').lastChild // look for main button
112
+ if (mainBtn) { mainBtn.click() ; event.preventDefault() } // click if found
90
113
  }
91
114
  }
92
115
  }
93
116
  },
94
117
 
95
118
  drag: {
96
- mousedown(event) { // find modal, attach listeners, init XY offsets
119
+ mousedown(event) { // find modal, update styles, attach listeners, init XY offsets
97
120
  if (event.button != 0) return // prevent non-left-click drag
98
121
  if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
99
- chatgpt.draggableElem = event.currentTarget
100
- Object.assign(chatgpt.draggableElem.style, {
101
- cursor: 'grabbing', transition: '0.1s', willChange: 'transform', transform: 'scale(1.05)' })
102
- event.preventDefault(); // prevent sub-elems like icons being draggable
103
- ['mousemove', 'mouseup'].forEach(eventType =>
122
+ chatgpt.draggingModal = event.currentTarget
123
+ event.preventDefault() // prevent sub-elems like icons being draggable
124
+ Object.assign(chatgpt.draggingModal.style, {
125
+ cursor: 'grabbing', transition: '0.1s', willChange: 'transform', transform: 'scale(1.05)' });
126
+ [...chatgpt.draggingModal.children] // prevent hover FX if drag lags behind cursor
127
+ .forEach(child => child.style.pointerEvents = 'none');
128
+ ['mousemove', 'mouseup'].forEach(eventType => // add listeners
104
129
  document.addEventListener(eventType, handlers.drag[eventType]))
105
- const draggableElemRect = chatgpt.draggableElem.getBoundingClientRect()
106
- handlers.drag.offsetX = event.clientX - draggableElemRect.left +21
107
- handlers.drag.offsetY = event.clientY - draggableElemRect.top +12
130
+ const draggingModalRect = chatgpt.draggingModal.getBoundingClientRect()
131
+ handlers.drag.offsetX = event.clientX - draggingModalRect.left +21
132
+ handlers.drag.offsetY = event.clientY - draggingModalRect.top +12
108
133
  },
109
134
 
110
135
  mousemove(event) { // drag modal
111
- if (!chatgpt.draggableElem) return
136
+ if (!chatgpt.draggingModal) return
112
137
  const newX = event.clientX - handlers.drag.offsetX,
113
138
  newY = event.clientY - handlers.drag.offsetY
114
- Object.assign(chatgpt.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
139
+ Object.assign(chatgpt.draggingModal.style, { left: `${newX}px`, top: `${newY}px` })
115
140
  },
116
141
 
117
- mouseup() { // remove listeners, reset chatgpt.draggableElem
118
- Object.assign(chatgpt.draggableElem.style, {
142
+ mouseup() { // restore styles/pointer events, remove listeners, reset chatgpt.draggingModal
143
+ Object.assign(chatgpt.draggingModal.style, { // restore styles
119
144
  cursor: 'inherit', transition: 'inherit', willChange: 'auto', transform: 'scale(1)' });
120
- ['mousemove', 'mouseup'].forEach(eventType =>
145
+ [...chatgpt.draggingModal.children] // restore pointer events
146
+ .forEach(child => child.style.pointerEvents = '');
147
+ ['mousemove', 'mouseup'].forEach(eventType => // remove listeners
121
148
  document.removeEventListener(eventType, handlers.drag[eventType]))
122
- chatgpt.draggableElem = null
149
+ chatgpt.draggingModal = null
123
150
  }
124
151
  }
125
152
  }
126
153
 
127
- // Create modal parent/children elements
128
- const modalContainer = document.createElement('div');
129
- modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
130
- modalContainer.classList.add('chatgpt-modal'); // add class to main div
154
+ // Create modal parent/children elems
155
+ const modalContainer = document.createElement('div')
156
+ modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now()
157
+ modalContainer.classList.add('chatgpt-modal') // add class to main div
131
158
  const modal = document.createElement('div'),
132
159
  modalTitle = document.createElement('h2'),
133
- modalMessage = document.createElement('p');
160
+ modalMessage = document.createElement('p')
134
161
 
135
162
  // Create/append/update modal style (if missing or outdated)
136
163
  const thisUpdated = 1739338889852 // timestamp of last edit for this file's `modalStyle`
@@ -226,88 +253,85 @@ const chatgpt = {
226
253
  .chatgpt-modal input[type=checkbox]:focus {
227
254
  outline: none ; box-shadow: none ; -webkit-box-shadow: none ; -moz-box-shadow: none }
228
255
  .chatgpt-modal .checkbox-group label {
229
- font-size: 14px ; color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}`
256
+ cursor: pointer ; font-size: 14px ; color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}`
230
257
  )
231
258
  }
232
259
 
233
- // Insert text into elements
234
- modalTitle.innerText = title || '';
235
- modalMessage.innerText = msg || ''; chatgpt.renderHTML(modalMessage);
260
+ // Insert text into elems
261
+ modalTitle.innerText = title || '' ; modalMessage.innerText = msg || '' ; chatgpt.renderHTML(modalMessage)
236
262
 
237
263
  // Create/append buttons (if provided) to buttons div
238
- const modalButtons = document.createElement('div');
239
- modalButtons.classList.add('modal-buttons', 'no-mobile-tap-outline');
264
+ const modalButtons = document.createElement('div')
265
+ modalButtons.classList.add('modal-buttons', 'no-mobile-tap-outline')
240
266
  if (btns) { // are supplied
241
- if (!Array.isArray(btns)) btns = [btns]; // convert single button to array if necessary
267
+ if (!Array.isArray(btns)) btns = [btns] // convert single button to array if necessary
242
268
  btns.forEach((buttonFn) => { // create title-cased labels + attach listeners
243
- const button = document.createElement('button');
269
+ const button = document.createElement('button')
244
270
  button.textContent = buttonFn.name
245
271
  .replace(/[_-]\w/g, match => match.slice(1).toUpperCase()) // convert snake/kebab to camel case
246
272
  .replace(/([A-Z])/g, ' $1') // insert spaces
247
- .replace(/^\w/, firstChar => firstChar.toUpperCase()); // capitalize first letter
248
- button.onclick = () => { dismissAlert(); buttonFn(); };
249
- modalButtons.insertBefore(button, modalButtons.firstChild); // insert button to left
250
- });
273
+ .replace(/^\w/, firstChar => firstChar.toUpperCase()) // capitalize first letter
274
+ button.onclick = () => { dismissAlert() ; buttonFn() }
275
+ modalButtons.insertBefore(button, modalButtons.firstChild)
276
+ })
251
277
  }
252
278
 
253
279
  // Create/append OK/dismiss button to buttons div
254
- const dismissBtn = document.createElement('button');
255
- dismissBtn.textContent = btns ? 'Dismiss' : 'OK';
256
- modalButtons.insertBefore(dismissBtn, modalButtons.firstChild);
280
+ const dismissBtn = document.createElement('button')
281
+ dismissBtn.textContent = btns ? 'Dismiss' : 'OK'
282
+ modalButtons.insertBefore(dismissBtn, modalButtons.firstChild)
257
283
 
258
284
  // Highlight primary button
259
- modalButtons.lastChild.classList.add('primary-modal-btn');
285
+ modalButtons.lastChild.classList.add('primary-modal-btn')
260
286
 
261
287
  // Create/append checkbox (if provided) to checkbox group div
262
- const checkboxDiv = document.createElement('div');
288
+ const checkboxDiv = document.createElement('div')
263
289
  if (checkbox) { // is supplied
264
- checkboxDiv.classList.add('checkbox-group');
290
+ checkboxDiv.classList.add('checkbox-group')
265
291
  const checkboxFn = checkbox, // assign the named function to checkboxFn
266
- checkboxInput = document.createElement('input');
267
- checkboxInput.type = 'checkbox';
268
- checkboxInput.onchange = checkboxFn;
292
+ checkboxInput = document.createElement('input')
293
+ checkboxInput.type = 'checkbox' ; checkboxInput.onchange = checkboxFn
269
294
 
270
295
  // Create/show label
271
- const checkboxLabel = document.createElement('label');
272
- checkboxLabel.onclick = () => { checkboxInput.checked = !checkboxInput.checked; checkboxFn(); };
296
+ const checkboxLabel = document.createElement('label')
297
+ checkboxLabel.onclick = () => { checkboxInput.checked = !checkboxInput.checked ; checkboxFn() }
273
298
  checkboxLabel.textContent = checkboxFn.name.charAt(0).toUpperCase() // capitalize first char
274
299
  + checkboxFn.name.slice(1) // format remaining chars
275
300
  .replace(/([A-Z])/g, (match, letter) => ' ' + letter.toLowerCase()) // insert spaces, convert to lowercase
276
301
  .replace(/\b(\w+)nt\b/gi, '$1n\'t') // insert apostrophe in 'nt' suffixes
277
- .trim(); // trim leading/trailing spaces
302
+ .trim() // trim leading/trailing spaces
278
303
 
279
- checkboxDiv.append(checkboxInput); checkboxDiv.append(checkboxLabel);
304
+ checkboxDiv.append(checkboxInput) ; checkboxDiv.append(checkboxLabel)
280
305
  }
281
306
 
282
307
  // Create close button
283
- const closeBtn = document.createElement('div');
284
- closeBtn.title = 'Close'; closeBtn.classList.add('modal-close-btn', 'no-mobile-tap-outline');
285
- const closeSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
286
- closeSVG.setAttribute('height', '10px');
287
- closeSVG.setAttribute('viewBox', '0 0 14 14');
288
- closeSVG.setAttribute('fill', 'none');
289
- const closeSVGpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
290
- closeSVGpath.setAttribute('fill-rule', 'evenodd');
291
- closeSVGpath.setAttribute('clip-rule', 'evenodd');
292
- closeSVGpath.setAttribute('fill', chatgpt.isDarkMode() ? 'white' : 'black');
293
- closeSVGpath.setAttribute('d', 'M13.7071 1.70711C14.0976 1.31658 14.0976 0.683417 13.7071 0.292893C13.3166 -0.0976312 12.6834 -0.0976312 12.2929 0.292893L7 5.58579L1.70711 0.292893C1.31658 -0.0976312 0.683417 -0.0976312 0.292893 0.292893C-0.0976312 0.683417 -0.0976312 1.31658 0.292893 1.70711L5.58579 7L0.292893 12.2929C-0.0976312 12.6834 -0.0976312 13.3166 0.292893 13.7071C0.683417 14.0976 1.31658 14.0976 1.70711 13.7071L7 8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166 14.0976 12.6834 13.7071 12.2929L8.41421 7L13.7071 1.70711Z');
294
- closeSVG.append(closeSVGpath); closeBtn.append(closeSVG);
308
+ const closeBtn = document.createElement('div')
309
+ closeBtn.title = 'Close' ; closeBtn.classList.add('modal-close-btn', 'no-mobile-tap-outline')
310
+ const closeSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
311
+ closeSVG.setAttribute('height', '10px')
312
+ closeSVG.setAttribute('viewBox', '0 0 14 14')
313
+ closeSVG.setAttribute('fill', 'none')
314
+ const closeSVGpath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
315
+ closeSVGpath.setAttribute('fill-rule', 'evenodd')
316
+ closeSVGpath.setAttribute('clip-rule', 'evenodd')
317
+ closeSVGpath.setAttribute('fill', chatgpt.isDarkMode() ? 'white' : 'black')
318
+ closeSVGpath.setAttribute('d', 'M13.7071 1.70711C14.0976 1.31658 14.0976 0.683417 13.7071 0.292893C13.3166 -0.0976312 12.6834 -0.0976312 12.2929 0.292893L7 5.58579L1.70711 0.292893C1.31658 -0.0976312 0.683417 -0.0976312 0.292893 0.292893C-0.0976312 0.683417 -0.0976312 1.31658 0.292893 1.70711L5.58579 7L0.292893 12.2929C-0.0976312 12.6834 -0.0976312 13.3166 0.292893 13.7071C0.683417 14.0976 1.31658 14.0976 1.70711 13.7071L7 8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166 14.0976 12.6834 13.7071 12.2929L8.41421 7L13.7071 1.70711Z')
319
+ closeSVG.append(closeSVGpath) ; closeBtn.append(closeSVG)
295
320
 
296
321
  // Assemble/append div
297
- const modalElems = [closeBtn, modalTitle, modalMessage, checkboxDiv, modalButtons ];
298
- modalElems.forEach((elem) => { modal.append(elem); });
299
- modal.style.width = `${ width || 458 }px`;
300
- modalContainer.append(modal); document.body.append(modalContainer);
322
+ modal.append(closeBtn, modalTitle, modalMessage, checkboxDiv, modalButtons)
323
+ modal.style.width = `${ width || 458 }px`
324
+ modalContainer.append(modal) ; document.body.append(modalContainer)
301
325
 
302
326
  // Enqueue alert
303
- let alertQueue = JSON.parse(localStorage.alertQueue);
304
- alertQueue.push(modalContainer.id);
305
- localStorage.alertQueue = JSON.stringify(alertQueue);
327
+ let alertQueue = JSON.parse(localStorage.alertQueue)
328
+ alertQueue.push(modalContainer.id)
329
+ localStorage.alertQueue = JSON.stringify(alertQueue)
306
330
 
307
331
  // Show alert if none active
308
- modalContainer.style.display = 'none';
309
- if (alertQueue.length === 1) {
310
- modalContainer.style.display = '';
332
+ modalContainer.style.display = 'none'
333
+ if (alertQueue.length == 1) {
334
+ modalContainer.style.display = ''
311
335
  setTimeout(() => { // dim bg
312
336
  modal.parentNode.style.backgroundColor = `rgba(67,70,72,${ scheme == 'dark' ? 0.62 : 0.33 })`
313
337
  modal.parentNode.classList.add('animated')
@@ -315,9 +339,8 @@ const chatgpt = {
315
339
  }
316
340
 
317
341
  // Add listeners
318
- const dismissElems = [modalContainer, closeBtn, closeSVG, dismissBtn];
319
- dismissElems.forEach(elem => elem.onclick = handlers.dismiss.click);
320
- document.addEventListener('keydown', handlers.dismiss.key);
342
+ [modalContainer, closeBtn, closeSVG, dismissBtn].forEach(elem => elem.onclick = handlers.dismiss.click)
343
+ document.addEventListener('keydown', handlers.dismiss.key)
321
344
  modal.onmousedown = handlers.drag.mousedown // enable click-dragging
322
345
 
323
346
  // Define alert dismisser
@@ -348,78 +371,80 @@ const chatgpt = {
348
371
  },
349
372
 
350
373
  async askAndGetReply(query) {
351
- chatgpt.send(query); await chatgpt.isIdle();
352
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
374
+ chatgpt.send(query) ; await chatgpt.isIdle()
375
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
353
376
  },
354
377
 
355
378
  autoRefresh: {
356
379
  activate(interval) {
357
- if (this.isActive) { // already running, do nothing
358
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh already active!'); return; }
380
+ if (this.isActive) // already running, do nothing
381
+ return console.log(
382
+ `↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh already active!`)
359
383
 
360
- const autoRefresh = this;
384
+ const autoRefresh = this
361
385
 
362
386
  // Run main activate routine
363
- this.toggle.refreshFrame();
387
+ this.toggle.refreshFrame()
364
388
  const scheduleRefreshes = interval => {
365
- const randomDelay = Math.max(2, Math.floor(chatgpt.randomFloat() * 21 - 10)); // set random delay up to ±10 secs
389
+ const randomDelay = Math.max(2, Math.floor(chatgpt.randomFloat() * 21 - 10)) // set random delay up to ±10 secs
366
390
  autoRefresh.isActive = setTimeout(() => {
367
- const manifestScript = document.querySelector('script[src*="_ssgManifest.js"]');
391
+ const manifestScript = document.querySelector(chatgpt.selectors.ssgManifest)
368
392
  if (manifestScript) {
369
- document.querySelector('#refresh-frame').src = manifestScript.src + '?' + Date.now();
370
- console.log('↻ ChatGPT >> [' + autoRefresh.nowTimeStamp() + '] ChatGPT session refreshed');
393
+ document.querySelector('#refresh-frame').src = manifestScript.src + '?' + Date.now()
394
+ console.log(`↻ ChatGPT >> [${autoRefresh.nowTimeStamp()}] ChatGPT session refreshed`)
371
395
  }
372
- scheduleRefreshes(interval);
373
- }, (interval + randomDelay) * 1000);
396
+ scheduleRefreshes(interval)
397
+ }, (interval + randomDelay) * 1000)
374
398
  };
375
- scheduleRefreshes( interval ? parseInt(interval, 10) : 30 );
376
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh activated');
399
+ scheduleRefreshes( interval ? parseInt(interval, 10) : 30 )
400
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh activated`)
377
401
 
378
402
  // Add listener to send beacons in Chromium to thwart auto-discards if Page Visibility API supported
379
- if (navigator.userAgent.includes('Chrome') && typeof document.hidden !== 'undefined')
380
- document.addEventListener('visibilitychange', this.toggle.beacons);
403
+ if (navigator.userAgent.includes('Chrome') && typeof document.hidden != 'undefined')
404
+ document.addEventListener('visibilitychange', this.toggle.beacons)
381
405
  },
382
406
 
383
407
  deactivate() {
384
408
  if (this.isActive) {
385
- this.toggle.refreshFrame();
386
- document.removeEventListener('visibilitychange', this.toggle.beacons);
387
- clearTimeout(this.isActive); this.isActive = null;
388
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh de-activated');
389
- } else { console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh already inactive!'); }
409
+ this.toggle.refreshFrame()
410
+ document.removeEventListener('visibilitychange', this.toggle.beacons)
411
+ clearTimeout(this.isActive) ; this.isActive = null
412
+ console.log(`↻ ChatGPT >> [${ chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh de-activated`)
413
+ } else
414
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh already inactive!`)
390
415
  },
391
416
 
392
417
  nowTimeStamp() {
393
- const now = new Date();
394
- const hours = now.getHours() % 12 || 12; // Convert to 12-hour format
395
- let minutes = now.getMinutes(), seconds = now.getSeconds();
396
- if (minutes < 10) minutes = '0' + minutes; if (seconds < 10) seconds = '0' + seconds;
397
- const meridiem = now.getHours() < 12 ? 'AM' : 'PM';
398
- return hours + ':' + minutes + ':' + seconds + ' ' + meridiem;
418
+ const now = new Date()
419
+ const hours = now.getHours() % 12 || 12 // convert to 12h format
420
+ let minutes = now.getMinutes(), seconds = now.getSeconds()
421
+ if (minutes < 10) minutes = '0' + minutes; if (seconds < 10) seconds = '0' + seconds
422
+ const meridiem = now.getHours() < 12 ? 'AM' : 'PM'
423
+ return `${hours}:${minutes}:${seconds} ${meridiem}`
399
424
  },
400
425
 
401
426
  toggle: {
402
427
 
403
428
  beacons() {
404
429
  if (chatgpt.autoRefresh.beaconID) {
405
- clearInterval(chatgpt.autoRefresh.beaconID); chatgpt.autoRefresh.beaconID = null;
406
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacons de-activated');
430
+ clearInterval(chatgpt.autoRefresh.beaconID) ; chatgpt.autoRefresh.beaconID = null
431
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Beacons de-activated`)
407
432
  } else {
408
433
  chatgpt.autoRefresh.beaconID = setInterval(() => {
409
- navigator.sendBeacon('https://httpbin.org/post', new Uint8Array());
410
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacon sent');
411
- }, 90000);
412
- console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacons activated');
434
+ navigator.sendBeacon('https://httpbin.org/post', new Uint8Array())
435
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Beacon sent`)
436
+ }, 90000)
437
+ console.log(`ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Beacons activated`)
413
438
  }
414
439
  },
415
440
 
416
441
  refreshFrame() {
417
- let refreshFrame = document.querySelector('#refresh-frame');
418
- if (refreshFrame) refreshFrame.remove();
442
+ let refreshFrame = document.querySelector('#refresh-frame')
443
+ if (refreshFrame) refreshFrame.remove()
419
444
  else {
420
445
  refreshFrame = Object.assign(document.createElement('iframe'),
421
- { id: 'refresh-frame', style: 'display: none' });
422
- document.head.prepend(refreshFrame);
446
+ { id: 'refresh-frame', style: 'display: none' })
447
+ document.head.prepend(refreshFrame)
423
448
  }
424
449
  }
425
450
  }
@@ -427,167 +452,165 @@ const chatgpt = {
427
452
 
428
453
  browser: {
429
454
 
430
- isLightMode() { return window.matchMedia?.('(prefers-color-scheme: light)')?.matches; },
431
- isDarkMode() { return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches; },
432
- isChromium() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chromium'); },
433
- isChrome() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chrome'); },
434
- isEdge() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Edge'); },
435
- isBrave() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Brave'); },
436
- isFirefox() { return navigator.userAgent.includes('Firefox'); },
455
+ isLightMode() { return window.matchMedia?.('(prefers-color-scheme: light)')?.matches },
456
+ isDarkMode() { return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches },
457
+ isChromium() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chromium') },
458
+ isChrome() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chrome') },
459
+ isEdge() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Edge') },
460
+ isBrave() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Brave') },
461
+ isFirefox() { return navigator.userAgent.includes('Firefox') },
437
462
 
438
463
  isFullScreen() {
439
- const userAgentStr = navigator.userAgent;
464
+ const userAgentStr = navigator.userAgent
440
465
  return userAgentStr.includes('Chrome') ? window.matchMedia('(display-mode: fullscreen)').matches
441
466
  : userAgentStr.includes('Firefox') ? window.fullScreen
442
- : /MSIE|rv:/.test(userAgentStr) ? document.msFullscreenElement : document.webkitIsFullScreen;
467
+ : /MSIE|rv:/.test(userAgentStr) ? document.msFullscreenElement : document.webkitIsFullScreen
443
468
  },
444
469
 
445
470
  isMobile() {
446
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); }
471
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) }
447
472
  },
448
473
 
449
474
  async clearChats() { // back-end method
450
- return new Promise((resolve, reject) => {
475
+ return new Promise((resolve, reject) =>
451
476
  chatgpt.getAccessToken().then(token => {
452
- const xhr = new XMLHttpRequest();
453
- xhr.open('PATCH', chatgpt.endpoints.openAI.chats, true);
454
- xhr.setRequestHeader('Content-Type', 'application/json');
455
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
477
+ const xhr = new XMLHttpRequest()
478
+ xhr.open('PATCH', chatgpt.endpoints.openAI.chats, true)
479
+ xhr.setRequestHeader('Content-Type', 'application/json')
480
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
456
481
  xhr.onload = () => {
457
- if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot clear chats.');
458
- console.info('Chats successfully cleared'); resolve();
459
- };
460
- xhr.send(JSON.stringify({ is_visible: false }));
461
- }).catch(err => reject(new Error(err.message)));
462
- });
482
+ if (xhr.status != 200) return reject('🤖 chatgpt.js >> Request failed. Cannot clear chats.')
483
+ console.info('Chats successfully cleared') ; resolve()
484
+ }
485
+ xhr.send(JSON.stringify({ is_visible: false }))
486
+ }).catch(err => reject(new Error(err.message)))
487
+ )
463
488
  },
464
489
 
465
490
  code: {
466
491
  // Tip: Use template literals for easier passing of code arguments. Ensure backticks and `$`s are escaped (using `\`)
467
492
 
468
493
  async execute(code) {
469
- if (!code) return console.error('Code argument not supplied. Pass some code!');
470
- if (typeof code !== 'string') return console.error('Code argument must be a string!');
471
- chatgpt.send('Display the output as if you were terminal:\n\n' + code);
472
- console.info('Executing code...');
473
- await chatgpt.isIdle();
474
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
494
+ if (!code) return console.error('Code argument not supplied. Pass some code!')
495
+ if (typeof code != 'string') return console.error('Code argument must be a string!')
496
+ chatgpt.send('Display the output as if you were terminal:\n\n' + code)
497
+ console.info('Executing code...')
498
+ await chatgpt.isIdle()
499
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
475
500
  },
476
501
 
477
502
  extract(msg) { // extract pure code from response (targets last block)
478
- const codeBlocks = msg.match(/(?<=```.*\n)[\s\S]*?(?=```)/g);
479
- return codeBlocks ? codeBlocks[codeBlocks.length - 1] : msg;
503
+ const codeBlocks = msg.match(/(?<=```.*\n)[\s\S]*?(?=```)/g)
504
+ return codeBlocks ? codeBlocks[codeBlocks.length - 1] : msg
480
505
  },
481
506
 
482
507
  async isIdle(timeout = null) {
483
- const obsConfig = { childList: true, subtree: true },
484
- selectors = { msgDiv: 'div[data-message-author-role]',
485
- replyDiv: 'div[data-message-author-role=assistant]' };
508
+ const obsConfig = { childList: true, subtree: true }
486
509
 
487
510
  // Create promises
488
- const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
511
+ const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null
489
512
  const isIdlePromise = (async () => {
490
513
  await new Promise(resolve => { // when on convo page
491
- if (document.querySelector(selectors.msgDiv)) resolve();
514
+ if (document.querySelector(chatgpt.selectors.chatDivs.msg)) resolve()
492
515
  else new MutationObserver((_, obs) => {
493
- if (document.querySelector(selectors.msgDiv)) { obs.disconnect(); resolve(); }
494
- }).observe(document.body, obsConfig);
495
- });
496
- await new Promise(resolve => { // when reply starts generating
516
+ if (document.querySelector(chatgpt.selectors.chatDivs.msg)) { obs.disconnect() ; resolve() }
517
+ }).observe(document.body, obsConfig)
518
+ })
519
+ await new Promise(resolve => // when reply starts generating
497
520
  new MutationObserver((_, obs) => {
498
- if (chatgpt.getStopBtn()) { obs.disconnect(); resolve(); }
499
- }).observe(document.body, { childList: true, subtree: true });
500
- });
501
- const replyDivs = document.querySelectorAll(selectors.replyDiv),
502
- lastReplyDiv = replyDivs[replyDivs.length - 1];
503
- await new Promise(resolve => { // when code starts generating
521
+ if (chatgpt.getStopBtn()) { obs.disconnect() ; resolve() }
522
+ }).observe(document.body, { childList: true, subtree: true })
523
+ )
524
+ const replyDivs = document.querySelectorAll(chatgpt.selectors.chatDivs.reply),
525
+ lastReplyDiv = replyDivs[replyDivs.length - 1]
526
+ await new Promise(resolve => // when code starts generating
504
527
  new MutationObserver((_, obs) => {
505
- if (lastReplyDiv?.querySelector('pre')) { obs.disconnect(); resolve(); }
506
- }).observe(document.body, obsConfig);
507
- });
508
- return new Promise(resolve => { // when code stops generating
528
+ if (lastReplyDiv?.querySelector('pre')) { obs.disconnect() ; resolve() }
529
+ }).observe(document.body, obsConfig)
530
+ )
531
+ return new Promise(resolve => // when code stops generating
509
532
  new MutationObserver((_, obs) => {
510
533
  if (lastReplyDiv?.querySelector('pre')?.nextElementSibling // code block not last child of reply div
511
534
  || !chatgpt.getStopBtn() // ...or reply outright stopped generating
512
- ) { obs.disconnect(); resolve(true); }
513
- }).observe(document.body, obsConfig);
514
- });
515
- })();
535
+ ) { obs.disconnect() ; resolve(true) }
536
+ }).observe(document.body, obsConfig)
537
+ )
538
+ })()
516
539
 
517
- return await (timeoutPromise ? Promise.race([isIdlePromise, timeoutPromise]) : isIdlePromise);
540
+ return await (timeoutPromise ? Promise.race([isIdlePromise, timeoutPromise]) : isIdlePromise)
518
541
  },
519
542
 
520
543
  async minify(code) {
521
- if (!code) return console.error('Code argument not supplied. Pass some code!');
522
- if (typeof code !== 'string') return console.error('Code argument must be a string!');
523
- chatgpt.send('Minify the following code:\n\n' + code);
524
- console.info('Minifying code...');
525
- await chatgpt.isIdle();
526
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
544
+ if (!code) return console.error('Code argument not supplied. Pass some code!')
545
+ if (typeof code != 'string') return console.error('Code argument must be a string!')
546
+ chatgpt.send('Minify the following code:\n\n' + code)
547
+ console.info('Minifying code...')
548
+ await chatgpt.isIdle()
549
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
527
550
  },
528
551
 
529
552
  async obfuscate(code) {
530
- if (!code) return console.error('Code argument not supplied. Pass some code!');
531
- if (typeof code !== 'string') return console.error('Code argument must be a string!');
532
- chatgpt.send('Obfuscate the following code:\n\n' + code);
533
- console.info('Obfuscating code...');
534
- await chatgpt.isIdle();
535
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
553
+ if (!code) return console.error('Code argument not supplied. Pass some code!')
554
+ if (typeof code != 'string') return console.error('Code argument must be a string!')
555
+ chatgpt.send('Obfuscate the following code:\n\n' + code)
556
+ console.info('Obfuscating code...')
557
+ await chatgpt.isIdle()
558
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
536
559
  },
537
560
 
538
561
  async refactor(code, objective) {
539
- if (!code) return console.error('Code (1st) argument not supplied. Pass some code!');
540
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
541
- return console.error(`Argument ${ i + 1 } must be a string.`);
542
- chatgpt.send('Refactor the following code for ' + (objective || 'brevity') + ':\n\n' + code);
543
- console.info('Refactoring code...');
544
- await chatgpt.isIdle();
545
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
562
+ if (!code) return console.error('Code (1st) argument not supplied. Pass some code!')
563
+ for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
564
+ return console.error(`Argument ${ i + 1 } must be a string.`)
565
+ chatgpt.send(`Refactor the following code for ${ objective || 'brevity' }:\n\n${code}`)
566
+ console.info('Refactoring code...')
567
+ await chatgpt.isIdle()
568
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
546
569
  },
547
570
 
548
571
  async review(code) {
549
- if (!code) return console.error('Code argument not supplied. Pass some code!');
550
- if (typeof code !== 'string') return console.error('Code argument must be a string!');
551
- chatgpt.send('Review the following code for me:\n\n' + code);
552
- console.info('Reviewing code...');
553
- await chatgpt.isIdle();
554
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
572
+ if (!code) return console.error('Code argument not supplied. Pass some code!')
573
+ if (typeof code == 'string') return console.error('Code argument must be a string!')
574
+ chatgpt.send('Review the following code for me:\n\n' + code)
575
+ console.info('Reviewing code...')
576
+ await chatgpt.isIdle()
577
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
555
578
  },
556
579
 
557
580
  async unminify(code) {
558
- if (!code) return console.error('Code argument not supplied. Pass some code!');
559
- if (typeof code !== 'string') return console.error('Code argument must be a string!');
560
- chatgpt.send('Unminify the following code.:\n\n' + code);
561
- console.info('Unminifying code...');
562
- await chatgpt.isIdle();
563
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
581
+ if (!code) return console.error('Code argument not supplied. Pass some code!')
582
+ if (typeof code != 'string') return console.error('Code argument must be a string!')
583
+ chatgpt.send('Unminify the following code.:\n\n' + code)
584
+ console.info('Unminifying code...')
585
+ await chatgpt.isIdle()
586
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
564
587
  },
565
588
 
566
589
  async write(prompt, outputLang) {
567
- if (!prompt) return console.error('Prompt (1st) argument not supplied. Pass a prompt!');
568
- if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
569
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
570
- return console.error(`Argument ${ i + 1 } must be a string.`);
571
- chatgpt.send(prompt + '\n\nWrite this as code in ' + outputLang);
572
- console.info('Writing code...');
573
- await chatgpt.isIdle();
574
- return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
590
+ if (!prompt) return console.error('Prompt (1st) argument not supplied. Pass a prompt!')
591
+ if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!')
592
+ for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
593
+ return console.error(`Argument ${ i + 1 } must be a string.`)
594
+ chatgpt.send(`${prompt}\n\nWrite this as code in ${outputLang}`)
595
+ console.info('Writing code...')
596
+ await chatgpt.isIdle()
597
+ return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'))
575
598
  }
576
599
  },
577
600
 
578
- continue() { chatgpt.response.continue(); },
601
+ continue() { chatgpt.response.continue() },
579
602
 
580
603
  async detectLanguage(text) {
581
- if (!text) return console.error('Text argument not supplied. Pass some text!');
582
- if (typeof text !== 'string') return console.error('Text argument must be a string!');
583
- chatgpt.send('Detect the language of the following text:\n\n' + text
584
- + '\n\nOnly respond with the name of the language');
585
- console.info('Reviewing text...');
586
- await chatgpt.isIdle();
587
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
604
+ if (!text) return console.error('Text argument not supplied. Pass some text!')
605
+ if (typeof text != 'string') return console.error('Text argument must be a string!')
606
+ chatgpt.send(`Detect the language of the following text:\n\n${text}`
607
+ + '\n\nOnly respond with the name of the language')
608
+ console.info('Reviewing text...')
609
+ await chatgpt.isIdle()
610
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
588
611
  },
589
612
 
590
- executeCode() { chatgpt.code.execute(); },
613
+ executeCode() { chatgpt.code.execute() },
591
614
 
592
615
  async exportChat(chatToGet, format) {
593
616
  // chatToGet = 'active' (default) | 'latest' | index|title|id of chat to get
@@ -597,12 +620,12 @@ const chatgpt = {
597
620
  chatToGet = !chatToGet ? 'active' // default to 'active' if unpassed
598
621
  : Number.isInteger(chatToGet) || /^\d+$/.test(chatToGet) ? // else if string/int num passed
599
622
  parseInt(chatToGet, 10) // parse as integer
600
- : chatToGet; // else preserve non-num string as 'active', 'latest' or title/id of chat to get
601
- format = format.toLowerCase() || 'html'; // default to 'html' if unpassed
623
+ : chatToGet // else preserve non-num string as 'active', 'latest' or title/id of chat to get
624
+ format = format.toLowerCase() || 'html' // default to 'html' if unpassed
602
625
 
603
626
  // Create transcript + filename
604
- console.info('Generating transcript...');
605
- let transcript = '', filename;
627
+ console.info('Generating transcript...')
628
+ let transcript = '', filename
606
629
  if (/te?xt/.test(format)) { // generate plain transcript + filename for TXT export
607
630
 
608
631
  // Format filename using date/time
@@ -611,106 +634,104 @@ const chatgpt = {
611
634
  month = (now.getMonth() + 1).toString().padStart(2, '0'),
612
635
  year = now.getFullYear(),
613
636
  hour = now.getHours().toString().padStart(2, '0'),
614
- minute = now.getMinutes().toString().padStart(2, '0');
615
- filename = `ChatGPT_${ day }-${ month }-${ year }_${ hour }-${ minute }.txt`;
637
+ minute = now.getMinutes().toString().padStart(2, '0')
638
+ filename = `ChatGPT_${ day }-${ month }-${ year }_${ hour }-${ minute }.txt`
616
639
 
617
640
  // Create transcript from active chat
618
641
  if (chatToGet == 'active' && /\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(window.location.href)) {
619
- const chatDivs = document.querySelectorAll('main > div > div > div > div > div > div[class*=group]');
620
- if (!chatDivs.length) return console.error('Chat is empty!');
621
- const msgs = []; let isUserMsg = true;
622
- chatDivs.forEach((div) => {
623
- const sender = isUserMsg ? 'USER' : 'CHATGPT'; isUserMsg = !isUserMsg;
642
+ const chatDivs = document.querySelectorAll(chatgpt.selectors.chatDivs.convo)
643
+ if (!chatDivs.length) return console.error('Chat is empty!')
644
+ const msgs = [] ; let isUserMsg = true
645
+ chatDivs.forEach(div => {
646
+ const sender = isUserMsg ? 'USER' : 'CHATGPT'; isUserMsg = !isUserMsg
624
647
  const msg = Array.from(div.childNodes).map(node => node.innerText)
625
648
  .join('\n\n') // insert double line breaks between paragraphs
626
- .replace('Copy code', '');
627
- msgs.push(sender + ': ' + msg);
628
- });
629
- transcript = msgs.join('\n\n');
649
+ .replace('Copy code', '')
650
+ msgs.push(`${sender}: ${msg}`)
651
+ })
652
+ transcript = msgs.join('\n\n')
630
653
 
631
654
  // ...or from getChatData(chatToGet)
632
- } else {
633
- for (const entry of await chatgpt.getChatData(chatToGet, 'msg', 'both', 'all')) {
634
- transcript += `USER: ${ entry.user }\n\n`;
635
- transcript += `CHATGPT: ${ entry.chatgpt }\n\n`;
636
- }}
655
+ } else
656
+ for (const entry of await chatgpt.getChatData(chatToGet, 'msg', 'both', 'all'))
657
+ transcript += `USER: ${entry.user}\n\nCHATGPT: ${entry.chatgpt}\n\n`
637
658
 
638
659
  } else { // generate rich transcript + filename for HTML/MD/PDF export
639
660
 
640
661
  // Fetch HTML transcript from OpenAI
641
662
  const response = await fetch(await chatgpt.shareChat(chatToGet)),
642
- htmlContent = await response.text();
663
+ htmlContent = await response.text()
643
664
 
644
665
  // Format filename after <title>
645
666
  const parser = new DOMParser(),
646
- parsedHtml = parser.parseFromString(htmlContent, 'text/html');
647
- filename = `${ parsedHtml.querySelector('title').textContent || 'ChatGPT conversation' }.html`;
667
+ parsedHtml = parser.parseFromString(htmlContent, 'text/html')
668
+ filename = `${ parsedHtml.querySelector('title').textContent || 'ChatGPT conversation' }.html`
648
669
 
649
670
  // Convert relative CSS paths to absolute ones
650
- const cssLinks = parsedHtml.querySelectorAll('link[rel=stylesheet]');
671
+ const cssLinks = parsedHtml.querySelectorAll('link[rel=stylesheet]')
651
672
  cssLinks.forEach(link => {
652
- const href = link.getAttribute('href');
653
- if (href?.startsWith('/')) link.setAttribute('href', 'https://chat.openai.com' + href);
673
+ const href = link.getAttribute('href')
674
+ if (href?.startsWith('/')) link.setAttribute('href', 'https://chat.openai.com' + href)
654
675
  });
655
676
 
656
677
  // Serialize updated HTML to string
657
- transcript = new XMLSerializer().serializeToString(parsedHtml);
678
+ transcript = new XMLSerializer().serializeToString(parsedHtml)
658
679
  }
659
680
 
660
681
  // Export transcript
661
- console.info(`Exporting transcript as ${ format.toUpperCase() }...`);
682
+ console.info(`Exporting transcript as ${ format.toUpperCase() }...`)
662
683
  if (format == 'pdf') { // convert SVGs + launch PDF printer
663
684
 
664
685
  // Convert SVG icons to data URLs for proper PDF rendering
665
686
  transcript = transcript.replace(/<svg.*?<\/svg>/g, (match) => {
666
- const dataURL = 'data:image/svg+xml,' + encodeURIComponent(match);
667
- return `<img src="${ dataURL }">`;
668
- });
687
+ const dataURL = 'data:image/svg+xml,' + encodeURIComponent(match)
688
+ return `<img src="${ dataURL }">`
689
+ })
669
690
 
670
691
  // Launch PDF printer
671
- const transcriptPopup = window.open('', '', 'toolbar=0, location=0, menubar=0, height=600, width=800');
672
- transcriptPopup.document.write(transcript);
673
- setTimeout(() => { transcriptPopup.print({ toPDF: true }); }, 100);
692
+ const transcriptPopup = window.open('', '', 'toolbar=0, location=0, menubar=0, height=600, width=800')
693
+ transcriptPopup.document.write(transcript)
694
+ setTimeout(() => { transcriptPopup.print({ toPDF: true }) }, 100)
674
695
 
675
696
  } else { // auto-save to file
676
697
 
677
698
  if (format == 'md') { // remove extraneous HTML + fix file extension
678
- const mdMatch = /<.*<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?<[^/]/.exec(transcript)[1];
679
- transcript = mdMatch || transcript; filename = filename.replace('.html', '.md');
699
+ const mdMatch = /<.*<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?<[^/]/.exec(transcript)[1]
700
+ transcript = mdMatch || transcript; filename = filename.replace('.html', '.md')
680
701
  }
681
702
  const blob = new Blob([transcript],
682
- { type: 'text/' + ( format == 'html' ? 'html' : format == 'md' ? 'markdown' : 'plain' )});
683
- const link = document.createElement('a'), blobURL = URL.createObjectURL(blob);
684
- link.href = blobURL; link.download = filename; document.body.append(link);
685
- link.click(); document.body.removeChild(link); URL.revokeObjectURL(blobURL);
703
+ { type: 'text/' + ( format == 'html' ? 'html' : format == 'md' ? 'markdown' : 'plain' )})
704
+ const link = document.createElement('a'), blobURL = URL.createObjectURL(blob)
705
+ link.href = blobURL ; link.download = filename ; document.body.append(link)
706
+ link.click() ; document.body.removeChild(link) ; URL.revokeObjectURL(blobURL)
686
707
  }
687
708
  },
688
709
 
689
- extractCode() { chatgpt.code.extract(); },
690
- focusChatbar() { chatgpt.getChatBox()?.focus(); },
710
+ extractCode() { chatgpt.code.extract() },
711
+ focusChatbar() { chatgpt.getChatBox()?.focus() },
691
712
 
692
713
  footer: {
693
- get() { return document.querySelector('.min-h-4'); },
714
+ get() { return document.querySelector(chatgpt.selectors.footer) },
694
715
 
695
716
  hide() {
696
- const footer = chatgpt.footer.get();
697
- if (!footer) return console.error('Footer element not found!');
698
- if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!');
699
- footer.style.display = 'none';
717
+ const footer = chatgpt.footer.get()
718
+ if (!footer) return console.error('Footer element not found!')
719
+ if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!')
720
+ footer.style.display = 'none'
700
721
  },
701
722
 
702
723
  show() {
703
- const footer = chatgpt.footer.get();
704
- if (!footer) return console.error('Footer element not found!');
705
- if (footer.style.visibility != 'hidden') return console.info('Footer already shown!');
724
+ const footer = chatgpt.footer.get()
725
+ if (!footer) return console.error('Footer element not found!')
726
+ if (footer.style.visibility != 'hidden') return console.info('Footer already shown!')
706
727
  footer.style.display = 'inherit'
707
728
  }
708
729
  },
709
730
 
710
731
  generateRandomIP() {
711
- const ip = Array.from({length: 4}, () => Math.floor(chatgpt.randomFloat() * 256)).join('.');
712
- console.info('IP generated: ' + ip);
713
- return ip;
732
+ const ip = Array.from({length: 4}, () => Math.floor(chatgpt.randomFloat() * 256)).join('.')
733
+ console.info('IP generated: ' + ip)
734
+ return ip
714
735
  },
715
736
 
716
737
  get(targetType, targetName = '') {
@@ -718,87 +739,83 @@ const chatgpt = {
718
739
  // targetName = from get[targetName][targetType] methods, e.g. 'send'
719
740
 
720
741
  // Validate argument types to be string only
721
- if (typeof targetType !== 'string' || typeof targetName !== 'string') {
722
- throw new TypeError('Invalid arguments. Both arguments must be strings.'); }
742
+ if (typeof targetType != 'string' || typeof targetName != 'string')
743
+ throw new TypeError('Invalid arguments. Both arguments must be strings.')
723
744
 
724
745
  // Validate targetType
725
- if (!cjsTargetTypes.includes(targetType.toLowerCase())) {
726
- throw new Error('Invalid targetType: ' + targetType
727
- + '. Valid values are: ' + JSON.stringify(cjsTargetTypes)); }
746
+ if (!cjsTargetTypes.includes(targetType.toLowerCase()))
747
+ throw new Error(`Invalid targetType: ${targetType}. Valid values are: ${JSON.stringify(cjsTargetTypes)}`)
728
748
 
729
749
  // Validate targetName scoped to pre-validated targetType
730
- const targetNames = [], reTargetName = new RegExp('^get(.*)' + targetType + '$', 'i');
750
+ const targetNames = [], reTargetName = new RegExp(`^get(.*)${targetType}$`, 'i')
731
751
  for (const prop in chatgpt) {
732
752
  if (typeof chatgpt[prop] == 'function' && reTargetName.test(prop)) {
733
753
  targetNames.push( // add found targetName to valid array
734
- prop.replace(reTargetName, '$1').toLowerCase());
754
+ prop.replace(reTargetName, '$1').toLowerCase())
735
755
  }}
736
- if (!targetNames.includes(targetName.toLowerCase())) {
737
- throw new Error('Invalid targetName: ' + targetName + '. '
756
+ if (!targetNames.includes(targetName.toLowerCase()))
757
+ throw new Error(`Invalid targetName: ${targetName}. `
738
758
  + (targetNames.length > 0 ? 'Valid values are: ' + JSON.stringify(targetNames)
739
- : 'targetType ' + targetType.toLowerCase() + ' does not require additional options.'));
740
- }
759
+ : 'targetType ' + targetType.toLowerCase() + ' does not require additional options.'))
741
760
 
742
761
  // Call target function using pre-validated name components
743
- const targetFuncNameLower = ('get' + targetName + targetType).toLowerCase();
762
+ const targetFuncNameLower = ('get' + targetName + targetType).toLowerCase()
744
763
  const targetFuncName = Object.keys(this).find( // find originally cased target function name
745
- (name) => { return name.toLowerCase() == targetFuncNameLower; }); // test for match
746
- return this[targetFuncName](); // call found function
764
+ (name) => { return name.toLowerCase() == targetFuncNameLower }) // test for match
765
+ return this[targetFuncName]() // call found function
747
766
  },
748
767
 
749
768
  getAccessToken() {
750
769
  return new Promise((resolve, reject) => {
751
- if (Object.keys(chatgpt.openAIaccessToken).length > 0 && // populated
752
- (Date.parse(chatgpt.openAIaccessToken.expireDate) - Date.parse(new Date()) >= 0)) // not expired
753
- return resolve(chatgpt.openAIaccessToken.token);
754
- const xhr = new XMLHttpRequest();
755
- xhr.open('GET', chatgpt.endpoints.openAI.session, true);
756
- xhr.setRequestHeader('Content-Type', 'application/json');
770
+ if (chatgpt.accessToken && (Date.parse(chatgpt.accessToken.expireDate) - Date.parse(new Date()) >= 0))
771
+ return resolve(chatgpt.accessToken.token) // unexpired one exists already
772
+ const xhr = new XMLHttpRequest()
773
+ xhr.open('GET', chatgpt.endpoints.openAI.session, true)
774
+ xhr.setRequestHeader('Content-Type', 'application/json')
757
775
  xhr.onload = () => {
758
- if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve access token.');
759
- console.info('Token expiration: ' + new Date(JSON.parse(xhr.responseText).expires).toLocaleString().replace(',', ' at'));
760
- chatgpt.openAIaccessToken = {
761
- token: JSON.parse(xhr.responseText).accessToken,
762
- expireDate: JSON.parse(xhr.responseText).expires
763
- };
764
- return resolve(chatgpt.openAIaccessToken.token);
765
- };
766
- xhr.send();
767
- });
776
+ if (xhr.status != 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve access token.')
777
+ console.info(`Token expiration: ${
778
+ new Date(JSON.parse(xhr.responseText).expires).toLocaleString().replace(',', ' at')}`)
779
+ chatgpt.accessToken = {
780
+ token: JSON.parse(xhr.responseText).accessToken, expireDate: JSON.parse(xhr.responseText).expires }
781
+ resolve(chatgpt.accessToken.token)
782
+ }
783
+ xhr.send()
784
+ })
768
785
  },
769
786
 
770
787
  getAccountDetails(...details) {
771
788
  // details = [email|id|image|name|picture] = optional
772
789
 
773
790
  // Build details array
774
- const validDetails = [ 'email', 'id', 'image', 'name', 'picture' ];
791
+ const validDetails = [ 'email', 'id', 'image', 'name', 'picture' ]
775
792
  details = ( !arguments[0] ? validDetails // no details passed, populate w/ all valid ones
776
793
  : Array.isArray(arguments[0]) ? arguments[0] // details array passed, do nothing
777
- : Array.from(arguments) ); // details arg(s) passed, convert to array
794
+ : Array.from(arguments) ) // details arg(s) passed, convert to array
778
795
 
779
796
  // Validate detail args
780
- for (const detail of details) {
781
- if (!validDetails.includes(detail)) { return console.error(
782
- 'Invalid detail arg \'' + detail + '\' supplied. Valid details are:\n'
783
- + ' [' + validDetails + ']'); }}
797
+ for (const detail of details) if (!validDetails.includes(detail))
798
+ return console.error(
799
+ `Invalid detail arg '${detail}' supplied. Valid details are:\n`
800
+ + ` [${validDetails}]`)
784
801
 
785
802
  // Return account details
786
803
  return new Promise((resolve, reject) => {
787
- const xhr = new XMLHttpRequest();
788
- xhr.open('GET', chatgpt.endpoints.openAI.session, true);
789
- xhr.setRequestHeader('Content-Type', 'application/json');
804
+ const xhr = new XMLHttpRequest()
805
+ xhr.open('GET', chatgpt.endpoints.openAI.session, true)
806
+ xhr.setRequestHeader('Content-Type', 'application/json')
790
807
  xhr.onload = () => {
791
- if (xhr.status === 200) {
792
- const data = JSON.parse(xhr.responseText).user, detailsToReturn = {};
793
- for (const detail of details) detailsToReturn[detail] = data[detail];
794
- return resolve(detailsToReturn);
795
- } else return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve account details.');
796
- };
797
- xhr.send();
798
- });
808
+ if (xhr.status == 200) {
809
+ const data = JSON.parse(xhr.responseText).user, detailsToReturn = {}
810
+ for (const detail of details) detailsToReturn[detail] = data[detail]
811
+ return resolve(detailsToReturn)
812
+ } else return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve account details.')
813
+ }
814
+ xhr.send()
815
+ })
799
816
  },
800
817
 
801
- getChatBox() { return document.getElementById('prompt-textarea'); },
818
+ getChatBox() { return document.getElementById('prompt-textarea') },
802
819
 
803
820
  getChatData(chatToGet = 1, detailsToGet = 'all', sender = 'all', msgToGet = 'all') {
804
821
  // chatToGet = 'active' | 'latest' | index|title|id of chat to get (defaults to active OpenAI chat > latest chat)
@@ -807,167 +824,164 @@ const chatgpt = {
807
824
  // msgToGet = 'all' | 'latest' | index of msg to get (defaults to 'all', requires 2nd param = 'msg')
808
825
 
809
826
  // Init args
810
- const validDetails = [ 'all', 'id', 'title', 'create_time', 'update_time', 'msg' ];
811
- const validSenders = [ 'all', 'both', 'user', 'chatgpt' ];
827
+ const validDetails = [ 'all', 'id', 'title', 'create_time', 'update_time', 'msg' ]
828
+ const validSenders = [ 'all', 'both', 'user', 'chatgpt' ]
812
829
  chatToGet = !chatToGet ? 'active' // if '' passed, set to active
813
830
  : Number.isInteger(chatToGet) || /^\d+$/.test(chatToGet) ? // else if string/int num passed
814
- ( parseInt(chatToGet, 10) === 0 ? 0 : parseInt(chatToGet, 10) - 1 ) // ...offset -1 or keep as 0
815
- : chatToGet; // else preserve non-num string as 'active', 'latest' or title/id of chat to get
831
+ ( parseInt(chatToGet, 10) == 0 ? 0 : parseInt(chatToGet, 10) - 1 ) // ...offset -1 or keep as 0
832
+ : chatToGet // else preserve non-num string as 'active', 'latest' or title/id of chat to get
816
833
  detailsToGet = ['all', ''].includes(detailsToGet) ? // if '' or 'all' passed
817
834
  validDetails.filter(detail => /^(?!all$|msg$).*/.test(detail)) // populate w/ [validDetails] except 'all' & 'msg'
818
- : Array.isArray(detailsToGet) ? detailsToGet : [detailsToGet]; // else convert to array if needed
835
+ : Array.isArray(detailsToGet) ? detailsToGet : [detailsToGet] // else convert to array if needed
819
836
  sender = !sender ? 'all' // if '' or unpassed, set to 'all'
820
- : validSenders.includes(sender) ? sender : 'invalid'; // else set to validSenders or 'invalid'
837
+ : validSenders.includes(sender) ? sender : 'invalid' // else set to validSenders or 'invalid'
821
838
  msgToGet = Number.isInteger(msgToGet) || /^\d+$/.test(msgToGet) ? // if string/int num passed
822
- ( parseInt(msgToGet, 10) === 0 ? 0 : parseInt(msgToGet, 10) - 1 ) // ...offset -1 or keep as 0
839
+ ( parseInt(msgToGet, 10) == 0 ? 0 : parseInt(msgToGet, 10) - 1 ) // ...offset -1 or keep as 0
823
840
  : ['all', 'latest'].includes(msgToGet.toLowerCase()) ? // else if 'all' or 'latest' passed
824
841
  msgToGet.toLowerCase() // ...preserve it
825
842
  : !msgToGet ? 'all' // else if '', set to 'all'
826
- : 'invalid'; // else set 'invalid' for validation step
843
+ : 'invalid' // else set 'invalid' for validation step
827
844
 
828
845
  // Validate args
829
- for (const detail of detailsToGet) {
830
- if (!validDetails.includes(detail)) { return console.error(
831
- 'Invalid detail arg \'' + detail + '\' passed. Valid details are:\n'
832
- + ' [' + validDetails + ']'); }}
833
- if (sender == 'invalid') { return console.error(
846
+ for (const detail of detailsToGet)
847
+ if (!validDetails.includes(detail)) return console.error(
848
+ `Invalid detail arg '${detail}' passed. Valid details are:\n`
849
+ + ` [${validDetails}]`)
850
+ if (sender == 'invalid') return console.error(
834
851
  'Invalid sender arg passed. Valid senders are:\n'
835
- + ' [' + validSenders + ']'); }
836
- if (msgToGet == 'invalid') { return console.error(
837
- 'Invalid msgToGet arg passed. Valid msg\'s to get are:\n'
838
- + ' [ \'all\' | \'latest\' | index of msg to get ]'); }
852
+ + ` [${validSenders}]`)
853
+ if (msgToGet == 'invalid') return console.error(
854
+ `Invalid msgToGet arg passed. Valid msg's to get are:\n`
855
+ + ` [ 'all' | 'latest' | index of msg to get ]`)
839
856
 
840
857
  const getChatDetails = (token, detailsToGet) => {
841
- const re_chatID = /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/;
858
+ const re_chatID = /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/
842
859
  return new Promise((resolve, reject) => {
843
- const xhr = new XMLHttpRequest();
844
- xhr.open('GET', chatgpt.endpoints.openAI.chats, true);
845
- xhr.setRequestHeader('Content-Type', 'application/json');
846
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
860
+ const xhr = new XMLHttpRequest()
861
+ xhr.open('GET', chatgpt.endpoints.openAI.chats, true)
862
+ xhr.setRequestHeader('Content-Type', 'application/json')
863
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
847
864
  xhr.onload = () => {
848
- if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat details.');
849
- const data = JSON.parse(xhr.responseText).items;
850
- if (data.length <= 0) return reject('🤖 chatgpt.js >> Chat list is empty.');
851
- const detailsToReturn = {};
865
+ if (xhr.status != 200)
866
+ return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat details.')
867
+ const data = JSON.parse(xhr.responseText).items
868
+ if (data.length <= 0) return reject('🤖 chatgpt.js >> Chat list is empty.')
869
+ const detailsToReturn = {}
852
870
 
853
871
  // Return by index if num, 'latest', or 'active' passed but not truly active
854
872
  if (Number.isInteger(chatToGet) || chatToGet == 'latest' ||
855
- (chatToGet == 'active' && !new RegExp('\/' + re_chatID.source + '$').test(window.location.href))) {
856
- chatToGet = Number.isInteger(chatToGet) ? chatToGet : 0; // preserve index, otherwise get latest
857
- if (chatToGet > data.length) { // reject if index out-of-bounds
858
- return reject('🤖 chatgpt.js >> Chat with index ' + ( chatToGet + 1 )
859
- + ' is out of bounds. Only ' + data.length + ' chats exist!'); }
860
- for (const detail of detailsToGet) detailsToReturn[detail] = data[chatToGet][detail];
861
- return resolve(detailsToReturn);
873
+ (chatToGet == 'active' && !new RegExp(`\/${re_chatID.source}$`).test(location.href))) {
874
+ chatToGet = Number.isInteger(chatToGet) ? chatToGet : 0 // preserve index, otherwise get latest
875
+ if (chatToGet > data.length) // reject if index out-of-bounds
876
+ return reject(`🤖 chatgpt.js >> Chat with index ${ chatToGet + 1 }`
877
+ + ` is out of bounds. Only ${data.length} chats exist!`)
878
+ for (const detail of detailsToGet) detailsToReturn[detail] = data[chatToGet][detail]
879
+ return resolve(detailsToReturn)
862
880
  }
863
881
 
864
882
  // Return by title, ID or active chat
865
883
  const chatIdentifier = ( // determine to check by ID or title
866
- chatToGet == 'active' || new RegExp('^' + re_chatID.source + '$').test(chatToGet) ? 'id' : 'title' );
884
+ chatToGet == 'active' ||
885
+ new RegExp(`^${re_chatID.source}$`).test(chatToGet) ? 'id' : 'title' )
867
886
  if (chatToGet == 'active') // replace chatToGet w/ actual ID
868
- chatToGet = re_chatID.exec(window.location.href)[0];
869
- let idx, chatFound; // index of potentially found chat, flag if found
870
- for (idx = 0; idx < data.length; idx++) { // search for id/title to set chatFound flag
871
- if (data[idx][chatIdentifier] == chatToGet) { chatFound = true; break; }}
887
+ chatToGet = re_chatID.exec(window.location.href)[0]
888
+ let idx, chatFound // index of potentially found chat, flag if found
889
+ for (idx = 0 ; idx < data.length ; idx++) { // search for id/title to set chatFound flag
890
+ if (data[idx][chatIdentifier] == chatToGet) { chatFound = true ; break }}
872
891
  if (!chatFound) // exit
873
- return reject('🤖 chatgpt.js >> No chat with ' + chatIdentifier + ' = ' + chatToGet + ' found.');
874
- for (const detail of detailsToGet) detailsToReturn[detail] = data[idx][detail];
875
- return resolve(detailsToReturn);
876
- };
877
- xhr.send();
878
- });};
892
+ return reject(`🤖 chatgpt.js >> No chat with ${chatIdentifier} = ${chatToGet} found.`)
893
+ for (const detail of detailsToGet) detailsToReturn[detail] = data[idx][detail]
894
+ return resolve(detailsToReturn)
895
+ }
896
+ xhr.send()
897
+ })}
879
898
 
880
899
  const getChatMsgs = token => {
881
900
  return new Promise((resolve, reject) => {
882
- const xhr = new XMLHttpRequest();
901
+ const xhr = new XMLHttpRequest()
883
902
  getChatDetails(token, ['id']).then(chat => {
884
- xhr.open('GET', `${chatgpt.endpoints.openAI.chat}/${chat.id}`, true);
885
- xhr.setRequestHeader('Content-Type', 'application/json');
886
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
903
+ xhr.open('GET', `${chatgpt.endpoints.openAI.chat}/${chat.id}`, true)
904
+ xhr.setRequestHeader('Content-Type', 'application/json')
905
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
887
906
  xhr.onload = () => {
888
- if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat messages.');
907
+ if (xhr.status != 200)
908
+ return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat messages.')
889
909
 
890
910
  // Init const's
891
- const data = JSON.parse(xhr.responseText).mapping; // Get chat messages
892
- const userMessages = [], chatGPTMessages = [], msgsToReturn = [];
911
+ const data = JSON.parse(xhr.responseText).mapping // get chat messages
912
+ const userMessages = [], chatGPTMessages = [], msgsToReturn = []
893
913
 
894
914
  // Fill [userMessages]
895
915
  for (const key in data)
896
916
  if (data[key].message != null && data[key].message.author.role == 'user')
897
- userMessages.push({ id: data[key].id, msg: data[key].message });
898
- userMessages.sort((a, b) => a.msg.create_time - b.msg.create_time); // sort in chronological order
917
+ userMessages.push({ id: data[key].id, msg: data[key].message })
918
+ userMessages.sort((a, b) => a.msg.create_time - b.msg.create_time) // sort in chronological order
899
919
 
900
920
  if (parseInt(msgToGet, 10) + 1 > userMessages.length) // reject if index out of bounds
901
- return reject('🤖 chatgpt.js >> Message/response with index ' + ( msgToGet + 1)
902
- + ' is out of bounds. Only ' + userMessages.length + ' messages/responses exist!');
921
+ return reject(`🤖 chatgpt.js >> Message/response with index ${ msgToGet + 1 }`
922
+ + ` is out of bounds. Only ${userMessages.length} messages/responses exist!`)
903
923
 
904
924
  // Fill [chatGPTMessages]
905
925
  for (const userMessage of userMessages) {
906
- let sub = [];
926
+ let sub = []
907
927
  for (const key in data) {
908
- if (data[key].message != null && data[key].message.author.role == 'assistant' && data[key].parent == userMessage.id) {
909
- sub.push(data[key].message);
910
- }
928
+ if (data[key].message != null && data[key].message.author.role == 'assistant'
929
+ && data[key].parent == userMessage.id)
930
+ sub.push(data[key].message)
911
931
  }
912
- sub.sort((a, b) => a.create_time - b.create_time); // sort in chronological order
932
+ sub.sort((a, b) => a.create_time - b.create_time) // sort in chronological order
913
933
  sub = sub.map(x => { // pull out msgs after sorting
914
934
  switch(x.content.content_type) {
915
- case 'code': return x.content.text;
916
- case 'text': return x.content.parts[0];
917
- default: return;
935
+ case 'code': return x.content.text
936
+ case 'text': return x.content.parts[0]
937
+ default: return
918
938
  }
919
- });
920
- sub = sub.length === 1 ? sub[0] : sub; // convert not regenerated responses to strings
921
- chatGPTMessages.push(sub); // array of arrays (length > 1 = regenerated responses)
939
+ })
940
+ sub = sub.length == 1 ? sub[0] : sub // convert not regenerated responses to strings
941
+ chatGPTMessages.push(sub) // array of arrays (length > 1 = regenerated responses)
922
942
  }
923
943
 
924
944
  if (sender == 'user') // Fill [msgsToReturn] with user messages
925
945
  for (const userMessage in userMessages)
926
- msgsToReturn.push(userMessages[userMessage].msg.content.parts[0]);
946
+ msgsToReturn.push(userMessages[userMessage].msg.content.parts[0])
927
947
  else if (sender == 'chatgpt') // Fill [msgsToReturn] with ChatGPT responses
928
948
  for (const chatGPTMessage of chatGPTMessages)
929
949
  msgsToReturn.push(msgToGet == 'latest' ? chatGPTMessages[chatGPTMessages.length - 1] : chatGPTMessage );
930
950
  else { // Fill [msgsToReturn] with objects of user messages and chatgpt response(s)
931
- let i = 0;
951
+ let i = 0
932
952
  for (const message in userMessages) {
933
953
  msgsToReturn.push({
934
954
  user: userMessages[message].msg.content.parts[0],
935
955
  chatgpt: msgToGet == 'latest' ? chatGPTMessages[i][chatGPTMessages[i].length - 1] : chatGPTMessages[i]
936
- });
937
- i++;
956
+ })
957
+ i++
938
958
  }
939
959
  }
940
960
  return resolve(msgToGet == 'all' ? msgsToReturn // if 'all' passed, return array
941
961
  : msgToGet == 'latest' ? msgsToReturn[msgsToReturn.length - 1] // else if 'latest' passed, return latest
942
- : msgsToReturn[msgToGet] ); // else return element of array
943
- };
944
- xhr.send();
945
- });});};
962
+ : msgsToReturn[msgToGet] ) // else return element of array
963
+ }
964
+ xhr.send()
965
+ })})}
946
966
 
947
967
  // Return chat data
948
968
  return new Promise(resolve => chatgpt.getAccessToken().then(token => {
949
969
  return resolve(detailsToGet.includes('msg') ? getChatMsgs(token)
950
- : getChatDetails(token, detailsToGet));
951
- }));
970
+ : getChatDetails(token, detailsToGet))
971
+ }))
952
972
  },
953
973
 
954
- getChatInput() { return chatgpt.getChatBox().firstChild.innerText; },
955
- getContinueButton() { return document.querySelector('button.btn:has([d^="M4.47189"])'); },
956
- getFooterDiv() { return chatgpt.footer.get(); },
957
- getHeaderDiv() { return chatgpt.header.get(); },
958
- getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest'); },
959
- getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
960
-
961
- getNewChatButton() {
962
- return document.querySelector(
963
- 'button[data-testid*=new-chat-button],' // sidebar button (when logged in)
964
- + 'button:has([d^="M3.06957"]),' // Cycle Arrows icon (Temp chat mode)
965
- + 'button:has([d^="M15.6729"])' // Pencil icon (recorded chat mode)
966
- )
967
- },
968
-
969
- getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
970
- getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
974
+ getChatInput() { return chatgpt.getChatBox().firstChild.innerText },
975
+ getContinueButton() { return document.querySelector(chatgpt.selectors.btns.continue) },
976
+ getErrorMsg() { return document.querySelector(`${chatgpt.selectors.errors.txt}:last-of-type`)?.innerText },
977
+ getFooterDiv() { return chatgpt.footer.get() },
978
+ getHeaderDiv() { return chatgpt.header.get() },
979
+ getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest') },
980
+ getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest') },
981
+ getLoginButton() { return document.querySelector(chatgpt.selectors.btns.login) },
982
+ getNewChatButton() { return document.querySelector(chatgpt.selectors.btns.newChat) },
983
+ getNewChatLink() { return document.querySelector(chatgpt.selectors.links.newChat) },
984
+ getRegenerateButton() { return document.querySelector(chatgpt.selectors.btns.regen) },
971
985
 
972
986
  getResponse() {
973
987
  // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
@@ -975,36 +989,38 @@ const chatgpt = {
975
989
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
976
990
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
977
991
 
978
- return chatgpt.response.get(...arguments);
992
+ return chatgpt.response.get(...arguments)
979
993
  },
980
994
 
981
- getResponseFromAPI(chatToGet, responseToGet) { return chatgpt.response.getFromAPI(chatToGet, responseToGet); },
982
- getResponseFromDOM(pos) { return chatgpt.response.getFromDOM(pos); },
983
- getScrollToBottomButton() { return document.querySelector('button:has([d^="M12 21C11.7348"])'); },
984
- getSendButton() { return document.querySelector('[data-testid=send-button]'); },
985
- getStopButton() { return document.querySelector('button[data-testid=stop-button]'); },
995
+ getResponseFromAPI(chatToGet, responseToGet) { return chatgpt.response.getFromAPI(chatToGet, responseToGet) },
996
+ getResponseFromDOM(pos) { return chatgpt.response.getFromDOM(pos) },
997
+ getScrollToBottomButton() { return document.querySelector(chatgpt.selectors.btns.scroll) },
998
+ getSendButton() { return document.querySelector(chatgpt.selectors.btns.send) },
999
+ getStopButton() { return document.querySelector(chatgpt.selectors.btns.stop) },
986
1000
 
987
1001
  getUserLanguage() {
988
- return navigator.languages[0] || navigator.language || navigator.browserLanguage ||
989
- navigator.systemLanguage || navigator.userLanguage || '';
1002
+ return navigator.languages[0] || navigator.language || navigator.browserLanguage
1003
+ || navigator.systemLanguage || navigator.userLanguage || ''
990
1004
  },
991
1005
 
1006
+ getVoiceButton() { return document.querySelector(chatgpt.selectors.btns.voice) },
1007
+
992
1008
  header: {
993
- get() { return document.querySelector('main .sticky'); },
994
- hide() { chatgpt.header.get().style.display = 'none'; },
995
- show() { chatgpt.header.get().style.display = 'flex'; }
1009
+ get() { return document.querySelector(chatgpt.selectors.header) },
1010
+ hide() { chatgpt.header.get().style.display = 'none' },
1011
+ show() { chatgpt.header.get().style.display = 'flex' }
996
1012
  },
997
1013
 
998
- hideFooter() { chatgpt.footer.hide(); },
999
- hideHeader() { chatgpt.header.hide(); },
1014
+ hideFooter() { chatgpt.footer.hide() },
1015
+ hideHeader() { chatgpt.header.hide() },
1000
1016
 
1001
1017
  history: {
1002
1018
  async isLoaded(timeout = null) {
1003
1019
  const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null
1004
1020
  const isLoadedPromise = new Promise(resolve => {
1005
- if (document.querySelector('nav')) resolve(true)
1021
+ if (document.querySelector(chatgpt.selectors.chatHistory)) resolve(true)
1006
1022
  else new MutationObserver((_, obs) => {
1007
- if (document.querySelector('nav')) { obs.disconnect() ; resolve(true) }
1023
+ if (document.querySelector(chatgpt.selectors.chatHistory)) { obs.disconnect() ; resolve(true) }
1008
1024
  }).observe(document.documentElement, { childList: true, subtree: true })
1009
1025
  })
1010
1026
  return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise )
@@ -1015,156 +1031,149 @@ const chatgpt = {
1015
1031
  // NOTE: DOM is not updated to reflect new instructions added/removed or toggle state (until session refresh)
1016
1032
 
1017
1033
  add(instruction, target) {
1018
- if (!instruction) return console.error('Please provide an instruction');
1019
- if (typeof instruction !== 'string') return console.error('Instruction must be a string');
1020
- const validTargets = ['user', 'chatgpt']; // valid targets
1021
- if (!target) return console.error('Please provide a valid target!');
1022
- if (typeof target !== 'string') return console.error('Target must be a string');
1023
- target = target.toLowerCase(); // lowercase target
1034
+ if (!instruction) return console.error('Please provide an instruction')
1035
+ if (typeof instruction != 'string') return console.error('Instruction must be a string')
1036
+ const validTargets = ['user', 'chatgpt'] // valid targets
1037
+ if (!target) return console.error('Please provide a valid target!')
1038
+ if (typeof target != 'string') return console.error('Target must be a string')
1039
+ target = target.toLowerCase() // lowercase target
1024
1040
  if (!validTargets.includes(target))
1025
- return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`);
1041
+ return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`)
1026
1042
 
1027
- instruction = `\n\n${instruction}`; // add 2 newlines to the new instruction
1043
+ instruction = `\n\n${instruction}` // add 2 newlines to the new instruction
1028
1044
 
1029
1045
  return new Promise(resolve => {
1030
1046
  chatgpt.getAccessToken().then(async token => {
1031
- const instructionsData = await this.fetchData();
1047
+ const instructionsData = await this.fetchData()
1032
1048
 
1033
1049
  // Concatenate old instructions with new instruction
1034
- if (target == 'user') instructionsData.about_user_message += instruction;
1035
- else if (target == 'chatgpt') instructionsData.about_model_message += instruction;
1050
+ if (target == 'user') instructionsData.about_user_message += instruction
1051
+ else if (target == 'chatgpt') instructionsData.about_model_message += instruction
1036
1052
 
1037
- await this.sendRequest('POST', token, instructionsData);
1053
+ await this.sendRequest('POST', token, instructionsData)
1038
1054
  return resolve();
1039
1055
  });
1040
1056
  });
1041
1057
  },
1042
1058
 
1043
1059
  clear(target) {
1044
- const validTargets = ['user', 'chatgpt']; // valid targets
1045
- if (!target) return console.error('Please provide a valid target!');
1046
- if (typeof target !== 'string') return console.error('Target must be a string');
1047
- target = target.toLowerCase(); // lowercase target
1060
+ const validTargets = ['user', 'chatgpt'] // valid targets
1061
+ if (!target) return console.error('Please provide a valid target!')
1062
+ if (typeof target != 'string') return console.error('Target must be a string')
1063
+ target = target.toLowerCase() // lowercase target
1048
1064
  if (!validTargets.includes(target))
1049
- return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`);
1065
+ return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`)
1050
1066
 
1051
1067
  return new Promise(resolve => {
1052
1068
  chatgpt.getAccessToken().then(async token => {
1053
- const instructionsData = await this.fetchData();
1069
+ const instructionsData = await this.fetchData()
1054
1070
 
1055
1071
  // Clear target's instructions
1056
- if (target == 'user') instructionsData.about_user_message = '';
1057
- else if (target == 'chatgpt') instructionsData.about_model_message = '';
1072
+ if (target == 'user') instructionsData.about_user_message = ''
1073
+ else if (target == 'chatgpt') instructionsData.about_model_message = ''
1058
1074
 
1059
- await this.sendRequest('POST', token, instructionsData);
1060
- return resolve();
1061
- });});
1075
+ await this.sendRequest('POST', token, instructionsData)
1076
+ return resolve()
1077
+ })})
1062
1078
  },
1063
1079
 
1064
1080
  fetchData() {
1065
1081
  // INTERNAL METHOD
1066
- return new Promise(resolve => {
1067
- chatgpt.getAccessToken().then(async token => {
1068
- return resolve(await this.sendRequest('GET', token)); // Return API data
1069
- });});
1082
+ return new Promise(resolve =>
1083
+ chatgpt.getAccessToken().then(async token =>
1084
+ resolve(await this.sendRequest('GET', token)))) // return API data
1070
1085
  },
1071
1086
 
1072
1087
  sendRequest(method, token, body) {
1073
1088
  // INTERNAL METHOD
1074
1089
  // Validate args
1075
- for (let i = 0; i < arguments.length - 1; i++) if (typeof arguments[i] !== 'string')
1076
- return console.error(`Argument ${ i + 1 } must be a string`);
1077
- const validMethods = ['POST', 'GET'];
1078
- method = (method || '').trim().toUpperCase();
1090
+ for (let i = 0 ; i < arguments.length - 1 ; i++) if (typeof arguments[i] == 'string')
1091
+ return console.error(`Argument ${ i + 1 } must be a string`)
1092
+ const validMethods = ['POST', 'GET']
1093
+ method = (method || '').trim().toUpperCase()
1079
1094
  if (!method || !validMethods.includes(method)) // reject if not valid method
1080
- return console.error(`Valid methods are ${ validMethods }`);
1081
- if (!token) return console.error('Please provide a valid access token!');
1082
- if (body && typeof body !== 'object') // reject if body is passed but not an object
1083
- return console.error(`Invalid body data type. Got ${ typeof body }, expected object`);
1095
+ return console.error(`Valid methods are ${ validMethods }`)
1096
+ if (!token) return console.error('Please provide a valid access token!')
1097
+ if (body && typeof body != 'object') // reject if body is passed but not an object
1098
+ return console.error(`Invalid body data type. Got ${ typeof body }, expected object`)
1084
1099
 
1085
1100
  return new Promise((resolve, reject) => {
1086
- const xhr = new XMLHttpRequest();
1087
- xhr.open(method, chatgpt.endpoints.openAI.instructions, true);
1101
+ const xhr = new XMLHttpRequest()
1102
+ xhr.open(method, chatgpt.endpoints.openAI.instructions, true)
1088
1103
  // Set headers
1089
- xhr.setRequestHeader('Accept-Language', 'en-US');
1090
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
1091
- if (method == 'POST') xhr.setRequestHeader('Content-Type', 'application/json');
1104
+ xhr.setRequestHeader('Accept-Language', 'en-US')
1105
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1106
+ if (method == 'POST') xhr.setRequestHeader('Content-Type', 'application/json')
1092
1107
 
1093
1108
  xhr.onload = () => {
1094
- const responseData = JSON.parse(xhr.responseText);
1095
- if (xhr.status === 422)
1109
+ const responseData = JSON.parse(xhr.responseText)
1110
+ if (xhr.status == 422)
1096
1111
  return reject('🤖 chatgpt.js >> Character limit exceeded. Custom instructions can have a maximum length of 1500 characters.');
1097
- else if (xhr.status === 403 && responseData.detail.reason == 'content_policy')
1098
- return reject('🤖 chatgpt.js >> ' + responseData.detail.description);
1099
- else if (xhr.status !== 200)
1100
- return reject('🤖 chatgpt.js >> Request failed. Cannot contact custom instructions endpoint.');
1101
- console.info(`Custom instructions successfully contacted with method ${ method }`);
1102
- return resolve(responseData || {}); // return response data no matter what the method is
1103
- };
1104
- xhr.send(JSON.stringify(body) || ''); // if body is passed send it, else just send the request
1105
- });
1112
+ else if (xhr.status == 403 && responseData.detail.reason == 'content_policy')
1113
+ return reject('🤖 chatgpt.js >> ' + responseData.detail.description)
1114
+ else if (xhr.status != 200)
1115
+ return reject('🤖 chatgpt.js >> Request failed. Cannot contact custom instructions endpoint.')
1116
+ console.info(`Custom instructions successfully contacted with method ${ method }`)
1117
+ return resolve(responseData || {}) // return response data no matter what the method is
1118
+ }
1119
+ xhr.send(JSON.stringify(body) || '') // if body is passed send it, else just send the request
1120
+ })
1106
1121
  },
1107
1122
 
1108
1123
  turnOff() {
1109
- return new Promise(resolve => {
1110
- chatgpt.getAccessToken().then(async token => {
1111
- const instructionsData = await this.fetchData();
1112
- instructionsData.enabled = false;
1113
- await this.sendRequest('POST', token, instructionsData);
1114
- return resolve();
1115
- });
1116
- });
1124
+ return new Promise(resolve => chatgpt.getAccessToken().then(async token => {
1125
+ const instructionsData = await this.fetchData()
1126
+ instructionsData.enabled = false
1127
+ await this.sendRequest('POST', token, instructionsData)
1128
+ return resolve()
1129
+ }))
1117
1130
  },
1118
1131
 
1119
1132
  turnOn() {
1120
- return new Promise(resolve => {
1121
- chatgpt.getAccessToken().then(async token => {
1122
- const instructionsData = await this.fetchData();
1123
- instructionsData.enabled = true;
1124
- await this.sendRequest('POST', token, instructionsData);
1125
- return resolve();
1126
- });
1127
- });
1133
+ return new Promise(resolve => chatgpt.getAccessToken().then(async token => {
1134
+ const instructionsData = await this.fetchData()
1135
+ instructionsData.enabled = true
1136
+ await this.sendRequest('POST', token, instructionsData)
1137
+ return resolve()
1138
+ }))
1128
1139
  },
1129
1140
 
1130
1141
  toggle() {
1131
- return new Promise(resolve => {
1132
- this.fetchData().then(async instructionsData => {
1133
- await (instructionsData.enabled ? this.turnOff() : this.turnOn());
1134
- return resolve();
1135
- });});
1142
+ return new Promise(resolve => this.fetchData().then(async instructionsData => {
1143
+ await (instructionsData.enabled ? this.turnOff() : this.turnOn())
1144
+ return resolve()
1145
+ }))
1136
1146
  }
1137
1147
  },
1138
1148
 
1139
1149
  isDarkMode() { return document.documentElement.className.includes('dark') },
1140
- isFullScreen() { return chatgpt.browser.isFullScreen(); },
1150
+ isFullScreen() { return chatgpt.browser.isFullScreen() },
1141
1151
 
1142
1152
  async isIdle(timeout = null) {
1143
- const obsConfig = { childList: true, subtree: true },
1144
- msgDivSelector = 'div[data-message-author-role]';
1153
+ const obsConfig = { childList: true, subtree: true }
1145
1154
 
1146
1155
  // Create promises
1147
- const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
1156
+ const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null
1148
1157
  const isIdlePromise = (async () => {
1149
1158
  await new Promise(resolve => { // when on convo page
1150
- if (document.querySelector(msgDivSelector)) resolve();
1159
+ if (document.querySelector(chatgpt.selectors.chatDivs.msg)) resolve()
1151
1160
  else new MutationObserver((_, obs) => {
1152
- if (document.querySelector(msgDivSelector)) { obs.disconnect(); resolve(); }
1153
- }).observe(document.body, obsConfig);
1154
- });
1155
- await new Promise(resolve => { // when reply starts generating
1161
+ if (document.querySelector(chatgpt.selectors.chatDivs.msg)) { obs.disconnect() ; resolve() }
1162
+ }).observe(document.body, obsConfig)
1163
+ })
1164
+ await new Promise(resolve => // when reply starts generating
1156
1165
  new MutationObserver((_, obs) => {
1157
- if (chatgpt.getStopBtn()) { obs.disconnect(); resolve(); }
1158
- }).observe(document.body, obsConfig);
1159
- });
1160
- return new Promise(resolve => { // when reply stops generating
1166
+ if (chatgpt.getStopBtn()) { obs.disconnect() ; resolve() }
1167
+ }).observe(document.body, obsConfig)
1168
+ )
1169
+ return new Promise(resolve => // when reply stops generating
1161
1170
  new MutationObserver((_, obs) => {
1162
- if (!chatgpt.getStopBtn()) { obs.disconnect(); resolve(true); }
1163
- }).observe(document.body, obsConfig);
1164
- });
1165
- })();
1171
+ if (!chatgpt.getStopBtn()) { obs.disconnect() ; resolve(true) }
1172
+ }).observe(document.body, obsConfig)
1173
+ )
1174
+ })()
1166
1175
 
1167
- return await (timeoutPromise ? Promise.race([isIdlePromise, timeoutPromise]) : isIdlePromise);
1176
+ return await (timeoutPromise ? Promise.race([isIdlePromise, timeoutPromise]) : isIdlePromise)
1168
1177
  },
1169
1178
 
1170
1179
  async isLoaded(timeout = null) {
@@ -1178,107 +1187,92 @@ const chatgpt = {
1178
1187
  return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise )
1179
1188
  },
1180
1189
 
1181
- isLightMode() { return document.documentElement.classList.toString().includes('light'); },
1190
+ isLightMode() { return document.documentElement.classList.toString().includes('light') },
1191
+ isTempChat() { return location.search == '?temporary-chat=true' },
1182
1192
  isTyping() { return !!this.getStopButton() },
1183
-
1184
- logout() { window.location.href = 'https://chat.openai.com/auth/logout'; },
1193
+ login() { window.location.href = 'https://chat.openai.com/auth/login' },
1194
+ logout() { window.location.href = 'https://chat.openai.com/auth/logout' },
1185
1195
 
1186
1196
  menu: {
1187
- elements: [],
1188
- addedEvent: false,
1197
+ elems: [],
1189
1198
 
1190
- append(element, attrs = {}) {
1191
- // element = 'button' | 'dropdown' REQUIRED (no default value)
1199
+ append(elem, attrs = {}) {
1200
+ // elem = 'button' | 'dropdown' REQUIRED (no default value)
1192
1201
  // attrs = { ... }
1193
1202
  // attrs for 'button': 'icon' = src string, 'label' = string, 'onclick' = function
1194
1203
  // attrs for 'dropdown': 'items' = [ { text: string, value: string }, ... ] array of objects
1195
1204
  // where 'text' is the displayed text of the option and 'value' is the value of the option
1196
1205
 
1197
- const validElements = ['button', 'dropdown'];
1198
- if (!element || typeof element !== 'string') // element not passed or invalid type
1199
- return console.error('🤖 chatgpt.js >> Please supply a valid string element name!');
1200
- element = element.toLowerCase();
1201
- if (!validElements.includes(element)) // element not in list
1202
- return console.error(`🤖 chatgpt.js >> Invalid element! Valid elements are [${validElements}]`);
1203
-
1204
- const newElement = document.createElement(
1205
- element == 'dropdown' ? 'select' :
1206
- element == 'button' ? 'a' : element
1207
- );
1208
- newElement.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now(); // add random id to the element
1209
-
1210
- if (element == 'button') {
1211
- newElement.textContent = attrs?.label && typeof attrs.label == 'string'
1212
- ? attrs.label
1213
- : 'chatgpt.js button';
1206
+ const validElems = ['button', 'dropdown']
1207
+ if (!elem || typeof elem != 'string') // element not passed or invalid type
1208
+ return console.error('🤖 chatgpt.js >> Please supply a valid string element name!')
1209
+ elem = elem.toLowerCase()
1210
+ if (!validElems.includes(elem)) // element not in list
1211
+ return console.error(`🤖 chatgpt.js >> Invalid element! Valid elems are [${validElems}]`)
1214
1212
 
1215
- const icon = document.createElement('img');
1213
+ const newElem = document.createElement(elem == 'dropdown' ? 'select' : elem == 'button' ? 'a' : elem)
1214
+ newElem.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now()
1215
+
1216
+ if (elem == 'button') {
1217
+ newElem.textContent = attrs?.label && typeof attrs.label == 'string' ? attrs.label : 'chatgpt.js button'
1218
+ const icon = document.createElement('img')
1216
1219
  icon.src = attrs?.icon && typeof attrs.icon == 'string' // can also be base64 encoded image string
1217
1220
  ? attrs.icon // add icon to button element if given, else default one
1218
- : ( chatgpt.endpoints.assets + '/starters/chrome/extension/icons/icon128.png' );
1219
- icon.width = 18;
1220
- newElement.insertBefore(icon, newElement.firstChild);
1221
-
1222
- newElement.onclick = attrs?.onclick && typeof attrs.onclick == 'function'
1223
- ? attrs.onclick
1224
- : function() {};
1221
+ : `${chatgpt.endpoints.assets}/starters/chrome/extension/icons/icon128.png`
1222
+ icon.width = 18
1223
+ newElem.firstChild.before(icon)
1224
+ newElem.onclick = attrs?.onclick && typeof attrs.onclick == 'function' ? attrs.onclick : function(){}
1225
1225
  }
1226
1226
 
1227
- else if (element == 'dropdown') {
1227
+ else if (elem == 'dropdown') {
1228
1228
  if (!attrs?.items || // there no are options to add
1229
1229
  !Array.isArray(attrs.items) || // it's not an array
1230
1230
  !attrs.items.length) // the array is empty
1231
- attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // set default dropdown entry
1231
+ attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }] // set default dropdown entry
1232
1232
 
1233
1233
  if (!attrs.items.every(el => typeof el == 'object')) // the entries of the array are not objects
1234
- return console.error('\'items\' must be an array of objects!');
1234
+ return console.error('\'items\' must be an array of objects!')
1235
1235
 
1236
- newElement.style = 'background-color: #000; width: 100%; border: none;';
1236
+ newElem.style = 'background-color: #000; width: 100%; border: none;'
1237
1237
 
1238
1238
  attrs.items.forEach(item => {
1239
- const optionElement = document.createElement('option');
1240
- optionElement.textContent = item?.text;
1241
- optionElement.value = item?.value;
1242
- newElement.add(optionElement);
1243
- });
1239
+ const optionElement = document.createElement('option')
1240
+ optionElement.textContent = item?.text
1241
+ optionElement.value = item?.value
1242
+ newElem.add(optionElement)
1243
+ })
1244
1244
  }
1245
1245
 
1246
- const addElementsToMenu = () => {
1247
- const optionButtons = document.querySelectorAll('a[role=menuitem]');
1248
- let cssClasses;
1249
-
1250
- for (const navLink of optionButtons)
1251
- if (navLink.textContent == 'Settings') {
1252
- cssClasses = navLink.classList;
1253
- break; }
1254
-
1255
- const headlessNav = optionButtons[0].parentNode;
1256
-
1257
- chatgpt.menu.elements.forEach(element => {
1258
- element.setAttribute('class', cssClasses);
1259
- if (!headlessNav.contains(element))
1260
- try { headlessNav.insertBefore(element, headlessNav.firstChild); }
1261
- catch (err) { console.error(err); }
1262
- });
1263
- };
1246
+ const addElemsToMenu = () => {
1247
+ const optionBtns = document.querySelectorAll('a[role=menuitem]')
1248
+ let cssClasses
1249
+ for (const navLink of optionBtns)
1250
+ if (navLink.textContent == 'Settings') { cssClasses = navLink.classList ; break }
1251
+ const headlessNav = optionBtns[0].parentNode
1252
+ chatgpt.menu.elems.forEach(elem => {
1253
+ elem.setAttribute('class', cssClasses)
1254
+ if (!headlessNav.contains(elem))
1255
+ try { headlessNav.firstChild.before(elem) }
1256
+ catch (err) { console.error(err) }
1257
+ })
1258
+ }
1264
1259
 
1265
- this.elements.push(newElement);
1266
- const menuBtn = document.querySelector('nav button[id*=headless]');
1260
+ this.elems.push(newElem)
1261
+ const menuBtn = document.querySelector('nav button[id*=headless]')
1267
1262
  if (!this.addedEvent) { // to prevent adding more than one event
1268
- menuBtn?.addEventListener('click', () => { setTimeout(addElementsToMenu, 25); });
1269
- this.addedEvent = true; }
1263
+ menuBtn?.addEventListener('click', () => setTimeout(addElemsToMenu, 25)) ; this.addedEvent = true }
1270
1264
 
1271
- return newElement.id; // Return the element id
1265
+ return newElem.id
1272
1266
  },
1273
1267
 
1274
1268
  close() {
1275
- try { document.querySelector('nav [id*=menu-button][aria-expanded=true]').click(); }
1276
- catch (err) { console.error(err.message); }
1269
+ try { document.querySelector('nav [id*=menu-button][aria-expanded=true]').click() }
1270
+ catch (err) { console.error(err.message) }
1277
1271
  },
1278
1272
 
1279
1273
  open() {
1280
- try { document.querySelector('nav [id*=menu-button][aria-expanded=false]').click(); }
1281
- catch (err) { console.error(err.message); }
1274
+ try { document.querySelector('nav [id*=menu-button][aria-expanded=false]').click() }
1275
+ catch (err) { console.error(err.message) }
1282
1276
  }
1283
1277
  },
1284
1278
 
@@ -1287,44 +1281,44 @@ const chatgpt = {
1287
1281
  notify(msg, position, notifDuration, shadow) {
1288
1282
  notifDuration = notifDuration ? +notifDuration : 1.75; // sec duration to maintain notification visibility
1289
1283
  const fadeDuration = 0.35, // sec duration of fade-out
1290
- vpYoffset = 23, vpXoffset = 27; // px offset from viewport border
1284
+ vpYoffset = 23, vpXoffset = 27 // px offset from viewport border
1291
1285
 
1292
1286
  // Create/append notification div
1293
- const notificationDiv = document.createElement('div'); // make div
1294
- notificationDiv.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
1295
- notificationDiv.classList.add('chatgpt-notif');
1296
- notificationDiv.innerText = msg; // insert msg
1297
- document.body.append(notificationDiv); // insert into DOM
1287
+ const notificationDiv = document.createElement('div') // make div
1288
+ notificationDiv.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now()
1289
+ notificationDiv.classList.add('chatgpt-notif')
1290
+ notificationDiv.innerText = msg // insert msg
1291
+ document.body.append(notificationDiv) // insert into DOM
1298
1292
 
1299
1293
  // Create/append close button
1300
- const closeBtn = document.createElement('div');
1301
- closeBtn.title = 'Dismiss'; closeBtn.classList.add('notif-close-btn', 'no-mobile-tap-outline');
1302
- const closeSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1303
- closeSVG.setAttribute('height', '8px');
1304
- closeSVG.setAttribute('viewBox', '0 0 14 14');
1305
- closeSVG.setAttribute('fill', 'none');
1306
- closeSVG.style.height = closeSVG.style.width = '8px'; // override SVG styles on non-OpenAI sites
1307
- const closeSVGpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1308
- closeSVGpath.setAttribute('fill-rule', 'evenodd');
1309
- closeSVGpath.setAttribute('clip-rule', 'evenodd');
1310
- closeSVGpath.setAttribute('fill', 'white');
1294
+ const closeBtn = document.createElement('div')
1295
+ closeBtn.title = 'Dismiss'; closeBtn.classList.add('notif-close-btn', 'no-mobile-tap-outline')
1296
+ const closeSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
1297
+ closeSVG.setAttribute('height', '8px')
1298
+ closeSVG.setAttribute('viewBox', '0 0 14 14')
1299
+ closeSVG.setAttribute('fill', 'none')
1300
+ closeSVG.style.height = closeSVG.style.width = '8px' // override SVG styles on non-OpenAI sites
1301
+ const closeSVGpath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
1302
+ closeSVGpath.setAttribute('fill-rule', 'evenodd')
1303
+ closeSVGpath.setAttribute('clip-rule', 'evenodd')
1304
+ closeSVGpath.setAttribute('fill', 'white')
1311
1305
  closeSVGpath.setAttribute('d', 'M13.7071 1.70711C14.0976 1.31658 14.0976 0.683417 13.7071 0.292893C13.3166 -0.0976312 12.6834 -0.0976312 12.2929 0.292893L7 5.58579L1.70711 0.292893C1.31658 -0.0976312 0.683417 -0.0976312 0.292893 0.292893C-0.0976312 0.683417 -0.0976312 1.31658 0.292893 1.70711L5.58579 7L0.292893 12.2929C-0.0976312 12.6834 -0.0976312 13.3166 0.292893 13.7071C0.683417 14.0976 1.31658 14.0976 1.70711 13.7071L7 8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166 14.0976 12.6834 13.7071 12.2929L8.41421 7L13.7071 1.70711Z');
1312
- closeSVG.append(closeSVGpath); closeBtn.append(closeSVG); notificationDiv.append(closeBtn);
1306
+ closeSVG.append(closeSVGpath) ; closeBtn.append(closeSVG) ; notificationDiv.append(closeBtn)
1313
1307
 
1314
1308
  // Determine div position/quadrant
1315
- notificationDiv.isTop = !position || !/low|bottom/i.test(position);
1316
- notificationDiv.isRight = !position || !/left/i.test(position);
1309
+ notificationDiv.isTop = !position || !/low|bottom/i.test(position)
1310
+ notificationDiv.isRight = !position || !/left/i.test(position)
1317
1311
  notificationDiv.quadrant = (notificationDiv.isTop ? 'top' : 'bottom')
1318
- + (notificationDiv.isRight ? 'Right' : 'Left');
1312
+ + (notificationDiv.isRight ? 'Right' : 'Left')
1319
1313
 
1320
1314
  // Create/append/update notification style (if missing or outdated)
1321
1315
  const thisUpdated = 1735767823541 // timestamp of last edit for this file's `notifStyle`
1322
- let notifStyle = document.querySelector('#chatgpt-notif-style'); // try to select existing style
1316
+ let notifStyle = document.querySelector('#chatgpt-notif-style') // try to select existing style
1323
1317
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1324
1318
  if (!notifStyle) { // outright missing, create/id/attr/append it first
1325
- notifStyle = document.createElement('style'); notifStyle.id = 'chatgpt-notif-style';
1326
- notifStyle.setAttribute('last-updated', thisUpdated.toString());
1327
- document.head.append(notifStyle);
1319
+ notifStyle = document.createElement('style') ; notifStyle.id = 'chatgpt-notif-style'
1320
+ notifStyle.setAttribute('last-updated', thisUpdated.toString())
1321
+ document.head.append(notifStyle)
1328
1322
  }
1329
1323
  notifStyle.innerText = ( // update prev/new style contents
1330
1324
  '.chatgpt-notif {'
@@ -1348,64 +1342,64 @@ const chatgpt = {
1348
1342
  + '15% { opacity: 0.35 ; transform: rotateX(-27deg) scale(1.05) }'
1349
1343
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1350
1344
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1351
- );
1345
+ )
1352
1346
  }
1353
1347
 
1354
1348
  // Enqueue notification
1355
- let notifyProps = JSON.parse(localStorage.notifyProps);
1356
- notifyProps.queue[notificationDiv.quadrant].push(notificationDiv.id);
1357
- localStorage.notifyProps = JSON.stringify(notifyProps);
1349
+ let notifyProps = JSON.parse(localStorage.notifyProps)
1350
+ notifyProps.queue[notificationDiv.quadrant].push(notificationDiv.id)
1351
+ localStorage.notifyProps = JSON.stringify(notifyProps)
1358
1352
 
1359
1353
  // Position notification (defaults to top-right)
1360
- notificationDiv.style.top = notificationDiv.isTop ? vpYoffset.toString() + 'px' : '';
1361
- notificationDiv.style.bottom = !notificationDiv.isTop ? vpYoffset.toString() + 'px' : '';
1362
- notificationDiv.style.right = notificationDiv.isRight ? vpXoffset.toString() + 'px' : '';
1363
- notificationDiv.style.left = !notificationDiv.isRight ? vpXoffset.toString() + 'px' : '';
1354
+ notificationDiv.style.top = notificationDiv.isTop ? vpYoffset.toString() + 'px' : ''
1355
+ notificationDiv.style.bottom = !notificationDiv.isTop ? vpYoffset.toString() + 'px' : ''
1356
+ notificationDiv.style.right = notificationDiv.isRight ? vpXoffset.toString() + 'px' : ''
1357
+ notificationDiv.style.left = !notificationDiv.isRight ? vpXoffset.toString() + 'px' : ''
1364
1358
 
1365
1359
  // Reposition old notifications
1366
- const thisQuadrantQueue = notifyProps.queue[notificationDiv.quadrant];
1360
+ const thisQuadrantQueue = notifyProps.queue[notificationDiv.quadrant]
1367
1361
  if (thisQuadrantQueue.length > 1) {
1368
1362
  try { // to move old notifications
1369
1363
  for (const divId of thisQuadrantQueue.slice(0, -1)) { // exclude new div
1370
1364
  const oldDiv = document.getElementById(divId),
1371
1365
  offsetProp = oldDiv.style.top ? 'top' : 'bottom', // pick property to change
1372
- vOffset = +/\d+/.exec(oldDiv.style[offsetProp])[0] + 5 + oldDiv.getBoundingClientRect().height;
1373
- oldDiv.style[offsetProp] = `${ vOffset }px`; // change prop
1366
+ vOffset = +/\d+/.exec(oldDiv.style[offsetProp])[0] + 5 + oldDiv.getBoundingClientRect().height
1367
+ oldDiv.style[offsetProp] = `${ vOffset }px` // change prop
1374
1368
  }
1375
1369
  } catch (err) {}
1376
1370
  }
1377
1371
 
1378
1372
  // Show notification
1379
1373
  setTimeout(() => {
1380
- notificationDiv.style.opacity = chatgpt.isDarkMode() ? 0.8 : 0.67; // show msg
1381
- notificationDiv.style.transform = 'translateX(0)'; // bring from off-screen
1382
- notificationDiv.style.transition = 'transform 0.15s ease, opacity 0.15s ease';
1383
- }, 10);
1374
+ notificationDiv.style.opacity = chatgpt.isDarkMode() ? 0.8 : 0.67 // show msg
1375
+ notificationDiv.style.transform = 'translateX(0)' // bring from off-screen
1376
+ notificationDiv.style.transition = 'transform 0.15s ease, opacity 0.15s ease'
1377
+ }, 10)
1384
1378
 
1385
1379
  // Init delay before hiding
1386
1380
  const hideDelay = fadeDuration > notifDuration ? 0 // don't delay if fade exceeds notification duration
1387
- : notifDuration - fadeDuration; // otherwise delay for difference
1381
+ : notifDuration - fadeDuration // otherwise delay for difference
1388
1382
 
1389
1383
  // Add notification dismissal to timeout schedule + button clicks
1390
1384
  const dismissNotif = () => {
1391
1385
  notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1392
- clearTimeout(dismissFuncTID);
1393
- };
1394
- const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1395
- closeSVG.onclick = dismissNotif; // add to close button clicks
1386
+ clearTimeout(dismissFuncTID)
1387
+ }
1388
+ const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000) // maintain visibility for `hideDelay` secs, then dismiss
1389
+ closeSVG.onclick = dismissNotif // add to close button clicks
1396
1390
 
1397
1391
  // Destroy notification
1398
1392
  notificationDiv.onanimationend = () => {
1399
- notificationDiv.remove(); // remove from DOM
1400
- notifyProps = JSON.parse(localStorage.notifyProps);
1401
- notifyProps.queue[notificationDiv.quadrant].shift(); // + memory
1402
- localStorage.notifyProps = JSON.stringify(notifyProps); // + storage
1403
- };
1393
+ notificationDiv.remove() // remove from DOM
1394
+ notifyProps = JSON.parse(localStorage.notifyProps)
1395
+ notifyProps.queue[notificationDiv.quadrant].shift() // + memory
1396
+ localStorage.notifyProps = JSON.stringify(notifyProps) // + storage
1397
+ }
1404
1398
 
1405
- return notificationDiv;
1399
+ return notificationDiv
1406
1400
  },
1407
1401
 
1408
- obfuscate() { chatgpt.code.obfuscate(); },
1402
+ obfuscate() { chatgpt.code.obfuscate() },
1409
1403
 
1410
1404
  printAllFunctions() {
1411
1405
 
@@ -1416,39 +1410,39 @@ const chatgpt = {
1416
1410
  methodName: ['#005aff', '#ffa500'], // blue, orange
1417
1411
  entryType: ['#467e06', '#b981f9'], // green, purple
1418
1412
  srcMethod: ['#ff0000', '#00ffff'] // red, cyan
1419
- };
1420
- Object.keys(colors).forEach(element => { // populate dark scheme colors if missing
1421
- colors[element][1] = colors[element][1] ||
1422
- '#' + (Number(`0x1${ colors[element][0].replace(/^#/, '') }`) ^ 0xFFFFFF)
1423
- .toString(16).substring(1).toUpperCase(); // convert to hex
1424
- });
1413
+ }
1414
+ Object.keys(colors).forEach(elem => { // populate dark scheme colors if missing
1415
+ colors[elem][1] = colors[elem][1] ||
1416
+ '#' + (Number(`0x1${ colors[elem][0].replace(/^#/, '') }`) ^ 0xFFFFFF)
1417
+ .toString(16).substring(1).toUpperCase() // convert to hex
1418
+ })
1425
1419
 
1426
1420
  // Create [functionNames]
1427
- const functionNames = [];
1421
+ const functionNames = []
1428
1422
  for (const prop in this) {
1429
1423
  if (typeof this[prop] == 'function') {
1430
1424
  const chatgptIsParent = !Object.keys(this)
1431
1425
  .find(obj => Object.keys(this[obj]).includes(this[prop].name))
1432
- const functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1433
- functionNames.push([functionParent, prop]);
1426
+ const functionParent = chatgptIsParent ? 'chatgpt' : 'other'
1427
+ functionNames.push([functionParent, prop])
1434
1428
  } else if (typeof this[prop] == 'object') {
1435
1429
  for (const nestedProp in this[prop]) {
1436
1430
  if (typeof this[prop][nestedProp] == 'function') {
1437
- functionNames.push([prop, nestedProp]);
1431
+ functionNames.push([prop, nestedProp])
1438
1432
  }}}}
1439
- functionNames.sort((a, b) => { return a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]); });
1433
+ functionNames.sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]))
1440
1434
 
1441
1435
  // Print methods
1442
1436
  const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches,
1443
- baseFontStyles = 'font-family: monospace ; font-size: larger ; ';
1444
- console.log('\n%c🤖 chatgpt.js methods\n', 'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold');
1437
+ baseFontStyles = 'font-family: monospace ; font-size: larger ; '
1438
+ console.log('\n%c🤖 chatgpt.js methods\n', 'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold')
1445
1439
  for (const functionName of functionNames) {
1446
1440
  const isChatGptObjParent = /chatgpt|other/.test(functionName[0]),
1447
1441
  rootFunction = ( functionName[0] == 'chatgpt' ? this[functionName[1]].name
1448
- : functionName[0] !== 'other' ? functionName[0] + '.' + functionName[1]
1442
+ : functionName[0] != 'other' ? functionName[0] + '.' + functionName[1]
1449
1443
  : (( Object.keys(this).find(obj => Object.keys(this[obj]).includes(this[functionName[1]].name)) + '.' )
1450
1444
  + this[functionName[1]].name )),
1451
- isAsync = this[functionName[1]]?.constructor.name == 'AsyncFunction';
1445
+ isAsync = this[functionName[1]]?.constructor.name == 'AsyncFunction'
1452
1446
  console.log('%c>> %c' + ( isChatGptObjParent ? '' : `${ functionName[0] }.%c`) + functionName[1]
1453
1447
  + ' - https://chatgptjs.org/userguide/' + /(?:.*\.)?(.*)/.exec(rootFunction)[1].toLowerCase() + ( isAsync ? '-async' : '' ) + '\n%c[%c'
1454
1448
  + ((( functionName[0] == 'chatgpt' && functionName[1] == this[functionName[1]].name ) || // parent is chatgpt + names match or
@@ -1470,28 +1464,28 @@ const chatgpt = {
1470
1464
  + 'color:' + ( isChatGptObjParent ? colors.srcMethod[+isDarkMode] : 'initial' ),
1471
1465
  baseFontStyles + ( isChatGptObjParent ? 'font-weight: initial' : 'font-style: italic' ) + ';'
1472
1466
  + 'color:' + ( isChatGptObjParent ? 'initial' : colors.srcMethod[+isDarkMode] ),
1473
- isChatGptObjParent ? '' : ( baseFontStyles + 'color: initial ; font-weight: initial' ));
1467
+ isChatGptObjParent ? '' : ( baseFontStyles + 'color: initial ; font-weight: initial' ))
1474
1468
  }
1475
1469
  },
1476
1470
 
1477
1471
  randomFloat() {
1478
1472
  // * Generates a random, cryptographically secure value between 0 (inclusive) & 1 (exclusive)
1479
- const crypto = window.crypto || window.msCrypto;
1480
- return crypto?.getRandomValues(new Uint32Array(1))[0] / 0xFFFFFFFF || Math.random();
1473
+ const crypto = window.crypto || window.msCrypto
1474
+ return crypto?.getRandomValues(new Uint32Array(1))[0] / 0xFFFFFFFF || Math.random()
1481
1475
  },
1482
1476
 
1483
- refactor() { chatgpt.code.refactor(); },
1484
- regenerate() { chatgpt.response.regenerate(); },
1477
+ refactor() { chatgpt.code.refactor() },
1478
+ regenerate() { chatgpt.response.regenerate() },
1485
1479
 
1486
1480
  renderHTML(node) {
1487
1481
  const reTags = /<([a-z\d]+)\b([^>]*)>([\s\S]*?)<\/\1>/g,
1488
- reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g, // eslint-disable-line
1489
- nodeContent = node.childNodes;
1482
+ reAttrs = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g, // eslint-disable-line
1483
+ nodeContent = node.childNodes
1490
1484
 
1491
1485
  // Preserve consecutive spaces + line breaks
1492
1486
  if (!chatgpt.renderHTML.preWrapSet) {
1493
- node.style.whiteSpace = 'pre-wrap'; chatgpt.renderHTML.preWrapSet = true;
1494
- setTimeout(() => { chatgpt.renderHTML.preWrapSet = false; }, 100);
1487
+ node.style.whiteSpace = 'pre-wrap' ; chatgpt.renderHTML.preWrapSet = true
1488
+ setTimeout(() => chatgpt.renderHTML.preWrapSet = false, 100)
1495
1489
  }
1496
1490
 
1497
1491
  // Process child nodes
@@ -1500,44 +1494,44 @@ const chatgpt = {
1500
1494
  // Process text node
1501
1495
  if (childNode.nodeType == Node.TEXT_NODE) {
1502
1496
  const text = childNode.nodeValue,
1503
- elems = Array.from(text.matchAll(reTags));
1497
+ elems = Array.from(text.matchAll(reTags))
1504
1498
 
1505
1499
  // Process 1st element to render
1506
1500
  if (elems.length > 0) {
1507
1501
  const elem = elems[0],
1508
- [tagContent, tagName, tagAttributes, tagText] = elem.slice(0, 4),
1509
- tagNode = document.createElement(tagName); tagNode.textContent = tagText;
1502
+ [tagContent, tagName, tagAttrs, tagText] = elem.slice(0, 4),
1503
+ tagNode = document.createElement(tagName) ; tagNode.textContent = tagText
1510
1504
 
1511
1505
  // Extract/set attributes
1512
- const attributes = Array.from(tagAttributes.matchAll(reAttributes));
1513
- attributes.forEach(attribute => {
1514
- const name = attribute[1], value = attribute[2].replace(/['"]/g, '');
1515
- tagNode.setAttribute(name, value);
1516
- });
1506
+ const attrs = Array.from(tagAttrs.matchAll(reAttrs))
1507
+ attrs.forEach(attr => {
1508
+ const name = attr[1], value = attr[2].replace(/['"]/g, '')
1509
+ tagNode.setAttribute(name, value)
1510
+ })
1517
1511
 
1518
- const renderedNode = chatgpt.renderHTML(tagNode); // render child elements of newly created node
1512
+ const renderedNode = chatgpt.renderHTML(tagNode) // render child elems of newly created node
1519
1513
 
1520
1514
  // Insert newly rendered node
1521
1515
  const beforeTextNode = document.createTextNode(text.substring(0, elem.index)),
1522
- afterTextNode = document.createTextNode(text.substring(elem.index + tagContent.length));
1516
+ afterTextNode = document.createTextNode(text.substring(elem.index + tagContent.length))
1523
1517
 
1524
1518
  // Replace text node with processed nodes
1525
- node.replaceChild(beforeTextNode, childNode);
1526
- node.insertBefore(renderedNode, beforeTextNode.nextSibling);
1527
- node.insertBefore(afterTextNode, renderedNode.nextSibling);
1519
+ node.replaceChild(beforeTextNode, childNode)
1520
+ node.insertBefore(renderedNode, beforeTextNode.nextSibling)
1521
+ node.insertBefore(afterTextNode, renderedNode.nextSibling)
1528
1522
  }
1529
1523
 
1530
1524
  // Process element nodes recursively
1531
- } else if (childNode.nodeType == Node.ELEMENT_NODE) chatgpt.renderHTML(childNode);
1525
+ } else if (childNode.nodeType == Node.ELEMENT_NODE) chatgpt.renderHTML(childNode)
1532
1526
  }
1533
1527
 
1534
- return node; // if assignment used
1528
+ return node // if assignment used
1535
1529
  },
1536
1530
 
1537
- async resend() { chatgpt.send(await chatgpt.getChatData('latest', 'msg', 'user', 'latest')); },
1531
+ async resend() { chatgpt.send(await chatgpt.getChatData('latest', 'msg', 'user', 'latest')) },
1538
1532
 
1539
1533
  response: {
1540
- continue() { try { chatgpt.getContinueBtn().click(); } catch (err) { console.error(err.message); }},
1534
+ continue() { try { chatgpt.getContinueBtn().click() } catch (err) { console.error(err.message) }},
1541
1535
 
1542
1536
  get() {
1543
1537
  // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
@@ -1546,25 +1540,25 @@ const chatgpt = {
1546
1540
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
1547
1541
 
1548
1542
  if (window.location.href.startsWith('https://chatgpt.com/c/'))
1549
- return this.getFromDOM.apply(null, arguments);
1550
- else return this.getFromAPI.apply(null, arguments);
1543
+ return this.getFromDOM.apply(null, arguments)
1544
+ else return this.getFromAPI.apply(null, arguments)
1551
1545
  },
1552
1546
 
1553
1547
  getFromAPI(chatToGet, responseToGet) {
1554
1548
  // chatToGet = index|title|id of chat to get (defaults to latest if '' or unpassed)
1555
1549
  // responseToGet = index of response to get (defaults to latest if '' or unpassed)
1556
1550
 
1557
- chatToGet = chatToGet || 'latest'; responseToGet = responseToGet || 'latest';
1558
- return chatgpt.getChatData(chatToGet, 'msg', 'chatgpt', responseToGet);
1551
+ chatToGet = chatToGet || 'latest'; responseToGet = responseToGet || 'latest'
1552
+ return chatgpt.getChatData(chatToGet, 'msg', 'chatgpt', responseToGet)
1559
1553
  },
1560
1554
 
1561
1555
  getFromDOM(pos) {
1562
1556
  const responseDivs = document.querySelectorAll('div[data-message-author-role=assistant]'),
1563
- strPos = pos.toString().toLowerCase();
1564
- let response = '';
1565
- if (!responseDivs.length) return console.error('No conversation found!');
1557
+ strPos = pos.toString().toLowerCase()
1558
+ let response = ''
1559
+ if (!responseDivs.length) return console.error('No conversation found!')
1566
1560
  if (/last|final/.test(strPos)) // get last response
1567
- response = responseDivs[responseDivs.length - 1].textContent;
1561
+ response = responseDivs[responseDivs.length - 1].textContent
1568
1562
  else { // get nth response
1569
1563
  const nthOfResponse = (
1570
1564
 
@@ -1587,140 +1581,142 @@ const chatgpt = {
1587
1581
  * ( /(?:ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1588
1582
  + ( /teen(?:th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1589
1583
 
1590
- );
1591
- response = responseDivs[nthOfResponse - 1].textContent;
1584
+ )
1585
+ response = responseDivs[nthOfResponse - 1].textContent
1592
1586
  }
1593
- response = response.replace(/^ChatGPT(?:ChatGPT)?/, ''); // strip sender name
1594
- return response;
1587
+ response = response.replace(/^ChatGPT(?:ChatGPT)?/, '') // strip sender name
1588
+ return response
1595
1589
  },
1596
1590
 
1597
- getLast() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
1598
- regenerate() { try { chatgpt.getRegenerateBtn().click(); } catch (err) { console.error(err.message); }},
1599
- stopGenerating() { try { chatgpt.getStopBtn().click(); } catch (err) { console.error(err.message); }}
1591
+ getLast() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest') },
1592
+ regenerate() { try { chatgpt.getRegenerateBtn().click() } catch (err) { console.error(err.message) }},
1593
+ stopGenerating() { try { chatgpt.getStopBtn().click() } catch (err) { console.error(err.message) }}
1600
1594
  },
1601
1595
 
1602
- reviewCode() { chatgpt.code.review(); },
1603
- scrollToBottom() { try { chatgpt.getScrollBtn().click(); } catch (err) { console.error(err.message); }},
1596
+ reviewCode() { chatgpt.code.review() },
1597
+ scrollToBottom() { try { chatgpt.getScrollBtn().click() } catch (err) { console.error(err.message) }},
1604
1598
 
1605
1599
  send(msg, method='') {
1606
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1607
- return console.error(`Argument ${ i + 1 } must be a string!`);
1608
- const textArea = chatgpt.getChatBox();
1609
- if (!textArea) return console.error('Chatbar element not found!');
1610
- const msgP = document.createElement('p'); msgP.textContent = msg;
1611
- textArea.replaceChild(msgP, textArea.querySelector('p'));
1612
- textArea.dispatchEvent(new Event('input', { bubbles: true })); // enable send button
1600
+ for (let i = 0 ; i < arguments.length ; i++) if (typeof arguments[i] != 'string')
1601
+ return console.error(`Argument ${ i + 1 } must be a string!`)
1602
+ const textArea = chatgpt.getChatBox()
1603
+ if (!textArea) return console.error('Chatbar element not found!')
1604
+ const msgP = document.createElement('p'); msgP.textContent = msg
1605
+ textArea.replaceChild(msgP, textArea.querySelector('p'))
1606
+ textArea.dispatchEvent(new Event('input', { bubbles: true })) // enable send button
1613
1607
  setTimeout(function delaySend() {
1614
- const sendBtn = chatgpt.getSendButton();
1608
+ const sendBtn = chatgpt.getSendButton()
1615
1609
  if (!sendBtn?.hasAttribute('disabled')) { // send msg
1616
1610
  method.toLowerCase() == 'click' || chatgpt.browser.isMobile() ? sendBtn.click()
1617
- : textArea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
1618
- } else setTimeout(delaySend, 222);
1619
- }, 222);
1611
+ : textArea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
1612
+ } else setTimeout(delaySend, 222)
1613
+ }, 222)
1620
1614
  },
1621
1615
 
1622
1616
  sendInNewChat(msg) {
1623
- if (typeof msg !== 'string') return console.error('Message must be a string!');
1624
- try { chatgpt.getNewChatBtn().click(); } catch (err) { return console.error(err.message); }
1625
- setTimeout(() => { chatgpt.send(msg); }, 500);
1617
+ if (typeof msg != 'string') return console.error('Message must be a string!')
1618
+ try { chatgpt.getNewChatBtn().click() } catch (err) { return console.error(err.message) }
1619
+ setTimeout(() => chatgpt.send(msg), 500)
1626
1620
  },
1627
1621
 
1628
1622
  settings: {
1629
1623
  scheme: {
1630
- isDark() { return document.documentElement.classList.contains('dark'); },
1631
- isLight() { return document.documentElement.classList.contains('light'); },
1624
+ isDark() { return document.documentElement.classList.contains('dark') },
1625
+ isLight() { return document.documentElement.classList.contains('light') },
1632
1626
  set(value) {
1633
1627
 
1634
1628
  // Validate value
1635
- const validValues = ['dark', 'light', 'system'];
1636
- if (!value) return console.error('Please specify a scheme value!');
1637
- if (!validValues.includes(value)) return console.error(`Invalid scheme value. Valid values are [${ validValues }]`);
1629
+ const validValues = ['dark', 'light', 'system']
1630
+ if (!value) return console.error('Please specify a scheme value!')
1631
+ if (!validValues.includes(value))
1632
+ return console.error(`Invalid scheme value. Valid values are [${ validValues }]`)
1638
1633
 
1639
1634
  // Determine scheme to set
1640
- let schemeToSet = value;
1641
- if (value == 'system') schemeToSet = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1642
- localStorage.setItem('theme', value);
1643
- console.info(`Scheme set to ${ value.toUpperCase() }.`);
1635
+ let schemeToSet = value
1636
+ if (value == 'system')
1637
+ schemeToSet = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
1638
+ localStorage.setItem('theme', value)
1639
+ console.info(`Scheme set to ${ value.toUpperCase() }.`)
1644
1640
 
1645
1641
  // Toggle scheme if necessary
1646
- if (!document.documentElement.classList.contains(schemeToSet)) this.toggle();
1642
+ if (!document.documentElement.classList.contains(schemeToSet)) this.toggle()
1647
1643
  },
1648
1644
  toggle() {
1649
- const [schemeToRemove, schemeToAdd] = this.isDark() ? ['dark', 'light'] : ['light', 'dark'];
1650
- document.documentElement.classList.replace(schemeToRemove, schemeToAdd);
1651
- document.documentElement.style.colorScheme = schemeToAdd;
1652
- localStorage.setItem('theme', schemeToAdd);
1645
+ const [schemeToRemove, schemeToAdd] = this.isDark() ? ['dark', 'light'] : ['light', 'dark']
1646
+ document.documentElement.classList.replace(schemeToRemove, schemeToAdd)
1647
+ document.documentElement.style.colorScheme = schemeToAdd
1648
+ localStorage.setItem('theme', schemeToAdd)
1653
1649
  }
1654
1650
  }
1655
1651
  },
1656
1652
 
1657
1653
  async sentiment(text, entity) {
1658
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1659
- return console.error(`Argument ${ i + 1 } must be a string.`);
1654
+ for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
1655
+ return console.error(`Argument ${ i + 1 } must be a string.`)
1660
1656
  chatgpt.send('What is the sentiment of the following text'
1661
1657
  + ( entity ? ` towards the entity ${ entity },` : '')
1662
- + ' from strongly negative to strongly positive?\n\n' + text );
1663
- console.info('Analyzing sentiment...');
1664
- await chatgpt.isIdle();
1665
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
1658
+ + ' from strongly negative to strongly positive?\n\n' + text )
1659
+ console.info('Analyzing sentiment...')
1660
+ await chatgpt.isIdle()
1661
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
1666
1662
  },
1667
1663
 
1668
- setScheme(value) { chatgpt.settings.scheme.set(value); },
1664
+ setScheme(value) { chatgpt.settings.scheme.set(value) },
1669
1665
 
1670
1666
  shareChat(chatToGet, method = 'clipboard') {
1671
1667
  // chatToGet = index|title|id of chat to get (defaults to latest if '' or unpassed)
1672
1668
  // method = [ 'alert'|'clipboard' ] (defaults to 'clipboard' if '' or unpassed)
1673
1669
 
1674
- const validMethods = ['alert', 'notify', 'notification', 'clipboard', 'copy'];
1670
+ const validMethods = ['alert', 'notify', 'notification', 'clipboard', 'copy']
1675
1671
  if (!validMethods.includes(method)) return console.error(
1676
- 'Invalid method \'' + method + '\' passed. Valid methods are [' + validMethods + '].');
1672
+ `Invalid method '${method}' passed. Valid methods are [${validMethods}].`)
1677
1673
 
1678
1674
  const getChatNode = token => {
1679
1675
  return new Promise((resolve, reject) => {
1680
- const xhr = new XMLHttpRequest();
1676
+ const xhr = new XMLHttpRequest()
1681
1677
  chatgpt.getChatData(chatToGet).then(chat => {
1682
- xhr.open('GET', `${ chatgpt.endpoints.openAI.chat }/${ chat.id }`, true);
1683
- xhr.setRequestHeader('Content-Type', 'application/json');
1684
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
1678
+ xhr.open('GET', `${ chatgpt.endpoints.openAI.chat }/${ chat.id }`, true)
1679
+ xhr.setRequestHeader('Content-Type', 'application/json')
1680
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1685
1681
  xhr.onload = () => {
1686
- if (xhr.status !== 200)
1687
- return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat node.');
1688
- return resolve(JSON.parse(xhr.responseText).current_node); // chat messages until now
1689
- };
1690
- xhr.send();
1691
- });});};
1682
+ if (xhr.status != 200)
1683
+ return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat node.')
1684
+ return resolve(JSON.parse(xhr.responseText).current_node) // chat messages until now
1685
+ }
1686
+ xhr.send()
1687
+ })})}
1692
1688
 
1693
1689
  const makeChatToShare = (token, node) => {
1694
1690
  return new Promise((resolve, reject) => {
1695
- const xhr = new XMLHttpRequest();
1691
+ const xhr = new XMLHttpRequest()
1696
1692
  chatgpt.getChatData(chatToGet).then(chat => {
1697
- xhr.open('POST', chatgpt.endpoints.openAI.share_create, true);
1698
- xhr.setRequestHeader('Content-Type', 'application/json');
1699
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
1693
+ xhr.open('POST', chatgpt.endpoints.openAI.share_create, true)
1694
+ xhr.setRequestHeader('Content-Type', 'application/json')
1695
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1700
1696
  xhr.onload = () => {
1701
- if (xhr.status !== 200)
1702
- return reject('🤖 chatgpt.js >> Request failed. Cannot initialize share chat.');
1703
- return resolve(JSON.parse(xhr.responseText)); // return untouched data
1704
- };
1697
+ if (xhr.status != 200)
1698
+ return reject('🤖 chatgpt.js >> Request failed. Cannot initialize share chat.')
1699
+ return resolve(JSON.parse(xhr.responseText)) // return untouched data
1700
+ }
1705
1701
  xhr.send(JSON.stringify({ // request body
1706
1702
  current_node_id: node, // by getChatNode
1707
1703
  conversation_id: chat.id, // current chat id
1708
1704
  is_anonymous: true // show user name in the conversation or not
1709
- }));
1710
- });});};
1705
+ }))
1706
+ })})}
1711
1707
 
1712
1708
  const confirmShareChat = (token, data) => {
1713
1709
  return new Promise((resolve, reject) => {
1714
- const xhr = new XMLHttpRequest();
1715
- xhr.open('PATCH', `${ chatgpt.endpoints.openAI.share }/${ data.share_id }`, true);
1716
- xhr.setRequestHeader('Content-Type', 'application/json');
1717
- xhr.setRequestHeader('Authorization', 'Bearer ' + token);
1710
+ const xhr = new XMLHttpRequest()
1711
+ xhr.open('PATCH', `${ chatgpt.endpoints.openAI.share }/${ data.share_id }`, true)
1712
+ xhr.setRequestHeader('Content-Type', 'application/json')
1713
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1718
1714
  xhr.onload = () => {
1719
- if (xhr.status !== 200)
1720
- return reject('🤖 chatgpt.js >> Request failed. Cannot share chat.');
1721
- console.info(`Chat shared at '${ data.share_url }'`);
1722
- return resolve(); // the response has nothing useful
1723
- };
1715
+ if (xhr.status != 200)
1716
+ return reject('🤖 chatgpt.js >> Request failed. Cannot share chat.')
1717
+ console.info(`Chat shared at '${ data.share_url }'`)
1718
+ return resolve() // the response has nothing useful
1719
+ }
1724
1720
  xhr.send(JSON.stringify({ // request body
1725
1721
  share_id: data.share_id,
1726
1722
  highlighted_message_id: data.highlighted_message_id,
@@ -1728,155 +1724,146 @@ const chatgpt = {
1728
1724
  is_public: true, // must be true or it'll cause a 404 error
1729
1725
  is_visible: data.is_visible,
1730
1726
  is_anonymous: data.is_anonymous
1731
- }));
1732
- });};
1727
+ }))
1728
+ })}
1733
1729
 
1734
1730
  return new Promise(resolve => {
1735
1731
  chatgpt.getAccessToken().then(token => { // get access token
1736
1732
  getChatNode(token).then(node => { // get chat node
1737
1733
  makeChatToShare(token, node).then(data => {
1738
1734
  confirmShareChat(token, data).then(() => {
1739
- if (['copy', 'clipboard'].includes(method)) navigator.clipboard.writeText(data.share_url);
1735
+ if (['copy', 'clipboard'].includes(method)) navigator.clipboard.writeText(data.share_url)
1740
1736
  else chatgpt.alert('🚀 Share link created!',
1741
- '"' + data.title + '" is available at: <a target="blank" rel="noopener" href="'
1742
- + data.share_url + '" >' + data.share_url + '</a>',
1743
- [ function openLink() { window.open(data.share_url, '_blank', 'noopener'); },
1744
- function copyLink() { navigator.clipboard.writeText(data.share_url); }]);
1745
- resolve(data.share_url);
1746
- });});});});});
1737
+ `"${data.title}" is available at: <a target="blank" rel="noopener" href="${
1738
+ data.share_url}">${data.share_url}</a>`,
1739
+ [ function openLink() { window.open(data.share_url, '_blank', 'noopener') },
1740
+ function copyLink() { navigator.clipboard.writeText(data.share_url) }])
1741
+ resolve(data.share_url)
1742
+ })})})})})
1747
1743
  },
1748
1744
 
1749
- showFooter() { chatgpt.footer.show(); },
1750
- showHeader() { chatgpt.header.show(); },
1745
+ showFooter() { chatgpt.footer.show() },
1746
+ showHeader() { chatgpt.header.show() },
1751
1747
 
1752
1748
  sidebar: {
1753
- elements: [], observer: {},
1749
+ elems: [], observer: {},
1754
1750
 
1755
1751
  activateObserver() {
1756
1752
 
1757
1753
  // Stop the previous observer to preserve resources
1758
1754
  if (this.observer instanceof MutationObserver)
1759
- try { this.observer.disconnect(); } catch (e) {}
1755
+ try { this.observer.disconnect() } catch (e) {}
1760
1756
 
1761
- if (!this.elements.length) return console.error('🤖 chatgpt.js >> No elements to append!');
1757
+ if (!this.elems.length) return console.error('🤖 chatgpt.js >> No elems to append!')
1762
1758
 
1763
- let cssClasses;
1764
- // Grab CSS from original website elements
1765
- for (let navLink of document.querySelectorAll('nav a')) {
1759
+ // Grab CSS from original website elems
1760
+ let cssClasses
1761
+ for (let navLink of document.querySelectorAll(chatgpt.selectors.links.sidebarItem))
1766
1762
  if (/.*chat/.exec(navLink.text)[0]) {
1767
- cssClasses = navLink.classList;
1768
- navLink.parentNode.style.margin = '2px 0'; // add v-margins to ensure consistency across all inserted buttons
1769
- break;
1763
+ cssClasses = navLink.classList
1764
+ navLink.parentNode.style.margin = '2px 0' // add v-margins for consistency across all inserted btns
1765
+ break
1770
1766
  }
1771
- }
1772
1767
 
1773
- // Apply CSS to make the added elements look like they belong to the website
1774
- this.elements.forEach(element => {
1775
- element.setAttribute('class', cssClasses);
1776
- element.style.maxHeight = element.style.minHeight = '44px'; // Fix the height of the element
1777
- element.style.margin = '2px 0';
1778
- });
1768
+ // Apply CSS to make the added elems look like they belong to the website
1769
+ this.elems.forEach(elem => {
1770
+ elem.setAttribute('class', cssClasses)
1771
+ elem.style.maxHeight = elem.style.minHeight = '44px' // fix the height of the element
1772
+ elem.style.margin = '2px 0'
1773
+ })
1779
1774
 
1780
1775
  // Create MutationObserver instance
1781
- const navBar = document.querySelector('nav');
1782
- if (!navBar) return console.error('Sidebar element not found!');
1783
- this.observer = new MutationObserver(mutations => {
1776
+ const navBar = document.querySelector(chatgpt.selectors.chatHistory)
1777
+ if (!navBar) return console.error('Sidebar element not found!')
1778
+ this.observer = new MutationObserver(mutations =>
1784
1779
  mutations.forEach(mutation => {
1785
1780
  if ((mutation.type == 'childList' && mutation.addedNodes.length) ||
1786
1781
  (mutation.type == 'attributes' && mutation.attributeName == 'data-chatgptjs')) // check for trigger
1787
- // Try to insert each element...
1788
- this.elements.forEach(element => {
1789
- // ...if it's not already present...
1790
- if (!navBar.contains(element))
1791
- try {
1792
- // ...at the top of the sidebar
1793
- navBar.insertBefore(element, navBar.querySelector('a').parentNode);
1794
- } catch (err) { console.error(err); }
1795
- });
1796
- });
1797
- });
1782
+ this.elems.forEach(elem => { // try to insert each element...
1783
+ if (!navBar.contains(elem)) // ...if it's not already present...
1784
+ try { navBar.querySelector('a').parentNode.before(elem) } // ...at top of sidebar
1785
+ catch (err) { console.error(err) }
1786
+ })
1787
+ })
1788
+ )
1798
1789
 
1799
- this.observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
1790
+ this.observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true })
1800
1791
  },
1801
1792
 
1802
- append(element, attrs = {}) {
1793
+ append(elem, attrs = {}) {
1803
1794
  // element = 'button' | 'dropdown' REQUIRED (no default value)
1804
1795
  // attrs = { ... }
1805
1796
  // attrs for 'button': 'icon' = src string, 'label' = string, 'onclick' = function
1806
1797
  // attrs for 'dropdown': 'items' = [ { text: string, value: string }, ... ] array of objects
1807
1798
  // where 'text' is the displayed text of the option and 'value' is the value of the option
1808
- const validElements = ['button', 'dropdown'];
1809
- if (!element || typeof element !== 'string') // Element not passed or invalid type
1810
- return console.error('🤖 chatgpt.js >> Please supply a valid string element name!');
1811
- element = element.toLowerCase();
1812
- if (!validElements.includes(element)) // Element not in list
1813
- return console.error(`🤖 chatgpt.js >> Invalid element! Valid elements are [${validElements}]`);
1814
-
1815
- const newElement = document.createElement(element == 'dropdown' ? 'select' : element);
1816
- newElement.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now(); // Add random id to the element
1817
-
1818
- if (element == 'button') {
1819
- newElement.textContent = attrs?.label && typeof attrs.label == 'string'
1799
+ const validElems = ['button', 'dropdown']
1800
+ if (!elem || typeof elem != 'string') // Element not passed or invalid type
1801
+ return console.error('🤖 chatgpt.js >> Please supply a valid string element name!')
1802
+ elem = elem.toLowerCase()
1803
+ if (!validElems.includes(elem)) // Element not in list
1804
+ return console.error(`🤖 chatgpt.js >> Invalid element! Valid elems are [${validElems}]`)
1805
+
1806
+ const newElem = document.createElement(elem == 'dropdown' ? 'select' : elem)
1807
+ newElem.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now() // Add random id to the element
1808
+
1809
+ if (elem == 'button') {
1810
+ newElem.textContent = attrs?.label && typeof attrs.label == 'string'
1820
1811
  ? attrs.label
1821
- : 'chatgpt.js button';
1822
-
1823
- const icon = document.createElement('img');
1812
+ : 'chatgpt.js button'
1813
+ const icon = document.createElement('img')
1824
1814
  icon.src = attrs?.icon && typeof attrs.icon == 'string' // Can also be base64 encoded image string
1825
1815
  ? attrs.icon // Add icon to button element if given, else default one
1826
- : ( chatgpt.endpoints.assets + '/starters/chrome/extension/icons/icon128.png' );
1827
- icon.width = 18;
1828
- newElement.insertBefore(icon, newElement.firstChild);
1829
-
1830
- newElement.onclick = attrs?.onclick && typeof attrs.onclick == 'function'
1831
- ? attrs.onclick
1832
- : function() {};
1816
+ : `${chatgpt.endpoints.assets}/starters/chrome/extension/icons/icon128.png`
1817
+ icon.width = 18
1818
+ newElem.firstChild.before(icon)
1819
+ newElem.onclick = attrs?.onclick && typeof attrs.onclick == 'function' ? attrs.onclick : function(){}
1833
1820
  }
1834
1821
 
1835
- else if (element == 'dropdown') {
1822
+ else if (elem == 'dropdown') {
1836
1823
  if (!attrs?.items || // There no are options to add
1837
1824
  !Array.isArray(attrs.items) || // It's not an array
1838
1825
  !attrs.items.length) // The array is empty
1839
- attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // Set default dropdown entry
1826
+ attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }] // Set default dropdown entry
1840
1827
 
1841
1828
  if (!attrs.items.every(el => typeof el == 'object')) // The entries of the array are not objects
1842
- return console.error('\'items\' must be an array of objects!');
1829
+ return console.error('\'items\' must be an array of objects!')
1843
1830
 
1844
1831
  attrs.items.forEach(item => {
1845
- const optionElement = document.createElement('option');
1846
- optionElement.textContent = item?.text;
1847
- optionElement.value = item?.value;
1848
- newElement.add(optionElement);
1849
- });
1832
+ const optionElement = document.createElement('option')
1833
+ optionElement.textContent = item?.text
1834
+ optionElement.value = item?.value
1835
+ newElem.add(optionElement)
1836
+ })
1850
1837
  }
1851
1838
 
1852
1839
 
1853
- // Fix for blank background on dropdown elements
1854
- if (element == 'dropdown') newElement.style.backgroundColor = 'var(--gray-900, rgb(32,33,35))';
1840
+ // Fix for blank background on dropdown elems
1841
+ if (elem == 'dropdown') newElem.style.backgroundColor = 'var(--gray-900, rgb(32,33,35))'
1855
1842
 
1856
- this.elements.push(newElement);
1857
- this.activateObserver();
1858
- document.body.setAttribute('data-chatgptjs', 'observer-trigger'); // add attribute to trigger the observer
1843
+ this.elems.push(newElem)
1844
+ this.activateObserver()
1845
+ document.body.setAttribute('data-chatgptjs', 'observer-trigger') // add attribute to trigger the observer
1859
1846
 
1860
- return newElement.id; // Return the element id
1847
+ return newElem.id // Return the element id
1861
1848
  },
1862
1849
 
1863
1850
  exists() { return !!chatgpt.getNewChatLink(); },
1864
- hide() { this.isOn() ? this.toggle() : console.info('Sidebar already hidden!'); },
1865
- show() { this.isOff() ? this.toggle() : console.info('Sidebar already shown!'); },
1866
- isOff() { return !this.isOn(); },
1851
+ hide() { this.isOn() ? this.toggle() : console.info('Sidebar already hidden!') },
1852
+ show() { this.isOff() ? this.toggle() : console.info('Sidebar already shown!') },
1853
+ isOff() { return !this.isOn() },
1867
1854
  isOn() {
1868
1855
  const sidebar = (() => {
1869
- return chatgpt.sidebar.exists() ? document.querySelector('[class*=sidebar]') : null; })();
1870
- if (!sidebar) { console.error('Sidebar element not found!'); return false; }
1856
+ return chatgpt.sidebar.exists() ? document.querySelector(chatgpt.selectors.sidebar) : null })()
1857
+ if (!sidebar) { console.error('Sidebar element not found!'); return false }
1871
1858
  else return chatgpt.browser.isMobile() ?
1872
1859
  document.documentElement.style.overflow == 'hidden'
1873
- : sidebar.style.visibility != 'hidden' && sidebar.style.width != '0px';
1860
+ : sidebar.style.visibility != 'hidden' && sidebar.style.width != '0px'
1874
1861
  },
1875
1862
 
1876
1863
  toggle() {
1877
- const sidebarToggle = document.querySelector('button[data-testid*=sidebar-button]');
1878
- if (!sidebarToggle) console.error('Sidebar toggle not found!');
1879
- sidebarToggle.click();
1864
+ const sidebarToggle = document.querySelector(chatgpt.selectors.btns.sidebar)
1865
+ if (!sidebarToggle) console.error('Sidebar toggle not found!')
1866
+ sidebarToggle.click()
1880
1867
  },
1881
1868
 
1882
1869
  async isLoaded(timeout = 5000) {
@@ -1892,18 +1879,18 @@ const chatgpt = {
1892
1879
  }
1893
1880
  },
1894
1881
 
1895
- startNewChat() { try { chatgpt.getNewChatBtn().click(); } catch (err) { console.error(err.message); }},
1882
+ startNewChat() { try { chatgpt.getNewChatBtn().click() } catch (err) { console.error(err.message) }},
1896
1883
  stop() { chatgpt.response.stopGenerating(); },
1897
1884
 
1898
1885
  async suggest(ideaType, details) {
1899
1886
  if (!ideaType) return console.error('ideaType (1st argument) not supplied'
1900
- + '(e.g. \'gifts\', \'names\', \'recipes\', etc.)');
1901
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1902
- return console.error(`Argument ${ i + 1 } must be a string.`);
1903
- chatgpt.send('Suggest some names. ' + ( details || '' ));
1904
- console.info(`Creating ${ ideaType }...`);
1905
- await chatgpt.isIdle();
1906
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
1887
+ + `(e.g. 'gifts', 'names', 'recipes', etc.)`)
1888
+ for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
1889
+ return console.error(`Argument ${ i + 1 } must be a string.`)
1890
+ chatgpt.send('Suggest some names. ' + ( details || '' ))
1891
+ console.info(`Creating ${ ideaType }...`)
1892
+ await chatgpt.isIdle()
1893
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
1907
1894
  },
1908
1895
 
1909
1896
  speak(msg, { voice = 2, pitch = 2, speed = 1.1, onend } = {} ) {
@@ -1934,72 +1921,72 @@ const chatgpt = {
1934
1921
  },
1935
1922
 
1936
1923
  async summarize(text) {
1937
- if (!text) return console.error('Text (1st) argument not supplied. Pass some text!');
1938
- if (typeof text !== 'string') return console.error('Text argument must be a string!');
1939
- chatgpt.send('Summarize the following text:\n\n' + text);
1940
- console.info('Summarizing text...');
1941
- await chatgpt.isIdle();
1942
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
1924
+ if (!text) return console.error('Text (1st) argument not supplied. Pass some text!')
1925
+ if (typeof text != 'string') return console.error('Text argument must be a string!')
1926
+ chatgpt.send('Summarize the following text:\n\n' + text)
1927
+ console.info('Summarizing text...')
1928
+ await chatgpt.isIdle()
1929
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
1943
1930
  },
1944
1931
 
1945
1932
  toggleScheme() { chatgpt.settings.scheme.toggle(); },
1946
1933
 
1947
1934
  async translate(text, outputLang) {
1948
- if (!text) return console.error('Text (1st) argument not supplied. Pass some text!');
1949
- if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
1950
- for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1951
- return console.error(`Argument ${ i + 1 } must be a string!`);
1952
- chatgpt.send('Translate the following text to ' + outputLang
1953
- + '. Only reply with the translation.\n\n' + text);
1954
- console.info('Translating text...');
1955
- await chatgpt.isIdle();
1956
- return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
1935
+ if (!text) return console.error('Text (1st) argument not supplied. Pass some text!')
1936
+ if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!')
1937
+ for (let i = 0 ; i < arguments.length ; i++) if (typeof arguments[i] != 'string')
1938
+ return console.error(`Argument ${ i + 1 } must be a string!`)
1939
+ chatgpt.send(`Translate the following text to ${outputLang}. Only reply with the translation.\n\n${text}`)
1940
+ console.info('Translating text...')
1941
+ await chatgpt.isIdle()
1942
+ return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
1957
1943
  },
1958
1944
 
1959
- unminify() { chatgpt.code.unminify(); },
1945
+ unminify() { chatgpt.code.unminify() },
1960
1946
 
1961
1947
  uuidv4() {
1962
1948
  try {
1963
1949
  // use native secure uuid generator when available
1964
- return crypto.randomUUID();
1950
+ return crypto.randomUUID()
1965
1951
  } catch(_e) {
1966
- let d = new Date().getTime(); // get current timestamp in ms (to ensure UUID uniqueness)
1952
+ let d = new Date().getTime() // get current timestamp in ms (to ensure UUID uniqueness)
1967
1953
  const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1968
1954
  const r = ( // generate random nibble
1969
- ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 );
1970
- d = Math.floor(d/16); // correspond each UUID digit to unique 4-bit chunks of timestamp
1955
+ ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 )
1956
+ d = Math.floor(d/16) // correspond each UUID digit to unique 4-bit chunks of timestamp
1971
1957
  return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16); // generate random hexadecimal digit
1972
- });
1973
- return uuid;
1958
+ })
1959
+ return uuid
1974
1960
  }
1975
1961
  },
1976
1962
 
1977
- writeCode() { chatgpt.code.write(); }
1978
- };
1963
+ writeCode() { chatgpt.code.write() }
1964
+ }
1979
1965
 
1980
- chatgpt.scheme = { ...chatgpt.settings.scheme }; // copy `chatgpt.settings.scheme` methods into `chatgpt.scheme`
1966
+ chatgpt.scheme = { ...chatgpt.settings.scheme } // copy `chatgpt.settings.scheme` methods into `chatgpt.scheme`
1981
1967
 
1982
1968
  // Create chatgpt.[actions]Button(identifier) functions
1983
- const cjsBtnActions = ['click', 'get'], cjsTargetTypes = [ 'button', 'link', 'div', 'response' ];
1969
+ const cjsBtnActions = ['click', 'get'], cjsTargetTypes = [ 'button', 'link', 'div', 'response' ]
1984
1970
  for (const btnAction of cjsBtnActions) {
1985
- chatgpt[btnAction + 'Button'] = function handleButton(buttonIdentifier) {
1986
- const button = /^[.#]/.test(buttonIdentifier) ? document.querySelector(buttonIdentifier)
1987
- : /send/i.test(buttonIdentifier) ? document.querySelector('form button[class*=bottom]')
1988
- : /scroll/i.test(buttonIdentifier) ? document.querySelector('button[class*=cursor]')
1989
- : (function() { // get via text content
1990
- for (const button of document.querySelectorAll('button')) { // try buttons
1991
- if (button.textContent.toLowerCase().includes(buttonIdentifier.toLowerCase())) {
1992
- return button; }}
1993
- for (const navLink of document.querySelectorAll('nav a')) { // try nav links if no button
1994
- if (navLink.textContent.toLowerCase().includes(buttonIdentifier.toLowerCase())) {
1995
- return navLink; }}})();
1996
- if (btnAction == 'click') { button.click(); } else { return button; }
1997
- };
1971
+ chatgpt[`${btnAction}Button`] = function handleButton(btnIdentifier) {
1972
+ const btn = /^[.#]/.test(btnIdentifier) ? document.querySelector(btnIdentifier)
1973
+ : /send/i.test(btnIdentifier) ? document.querySelector(chatgpt.selectors.btns.send)
1974
+ : /scroll/i.test(btnIdentifier) ? document.querySelector(chatgpt.selectors.btns.scroll)
1975
+ : (function() { // get via text content
1976
+ for (const btn of document.querySelectorAll('button'))
1977
+ if (btn.textContent.toLowerCase().includes(btnIdentifier.toLowerCase()))
1978
+ return btn
1979
+ for (const navLink of document.querySelectorAll(chatgpt.selectors.links.sidebarItem))
1980
+ if (navLink.textContent.toLowerCase().includes(btnIdentifier.toLowerCase()))
1981
+ return navLink
1982
+ })()
1983
+ if (btnAction == 'click') btn.click() ; else return btn
1984
+ }
1998
1985
  }
1999
1986
 
2000
1987
  // Create ALIAS functions
2001
1988
  const cjsFuncAliases = [
2002
- ['actAs', 'actas', 'act', 'become', 'persona', 'premadePrompt', 'preMadePrompt', 'prePrompt', 'preprompt', 'roleplay', 'rolePlay', 'rp'],
1989
+ ['actAs', 'act', 'become', 'persona', 'premadePrompt', 'preMadePrompt', 'prePrompt', 'rolePlay', 'rp'],
2003
1990
  ['activateAutoRefresh', 'activateAutoRefresher', 'activateRefresher', 'activateSessionRefresher',
2004
1991
  'autoRefresh', 'autoRefresher', 'autoRefreshSession', 'refresher', 'sessionRefresher'],
2005
1992
  ['continue', 'continueChat', 'continueGenerating', 'continueResponse'],
@@ -2015,8 +2002,9 @@ const cjsFuncAliases = [
2015
2002
  ['getScrollToBottomButton', 'getScrollButton'],
2016
2003
  ['getStopButton', 'getStopGeneratingButton'],
2017
2004
  ['getTextarea', 'getTextArea', 'getChatbar', 'getChatBar', 'getChatbox', 'getChatBox'],
2018
- ['isFullScreen', 'isFullscreen', 'isfullscreen'],
2019
- ['logout', 'logOut', 'logOff', 'signOff', 'signOut'],
2005
+ ['getVoiceButton', 'getVoiceModeButton'],
2006
+ ['isFullScreen', 'isFullscreen'],
2007
+ ['isTempChat', 'isIncognito', 'isIncognitoMode', 'isTempChatMode'],
2020
2008
  ['minify', 'codeMinify', 'minifyCode'],
2021
2009
  ['new', 'newChat', 'startNewChat'],
2022
2010
  ['obfuscate', 'codeObfuscate', 'obfuscateCode'],
@@ -2053,6 +2041,8 @@ const cjsFuncSynonyms = [
2053
2041
  ['execute', 'interpret', 'interpreter', 'run'],
2054
2042
  ['firefox', 'ff'],
2055
2043
  ['generating', 'generation'],
2044
+ ['login', 'logIn', 'logOn', 'signIn', 'signOn'],
2045
+ ['logout', 'logOut', 'logOff', 'signOff', 'signOut'],
2056
2046
  ['message', 'msg'],
2057
2047
  ['minify', 'uglify'],
2058
2048
  ['refactor', 'rewrite'],
@@ -2062,6 +2052,7 @@ const cjsFuncSynonyms = [
2062
2052
  ['sentiment', 'attitude', 'emotion', 'feeling', 'opinion', 'perception'],
2063
2053
  ['speak', 'play', 'say', 'speech', 'talk', 'tts'],
2064
2054
  ['summarize', 'tldr'],
2055
+ ['temp', 'temporary'],
2065
2056
  ['typing', 'generating'],
2066
2057
  ['unminify', 'beautify', 'prettify', 'prettyPrint']
2067
2058
  ];
@@ -2105,16 +2096,16 @@ function toCamelCase(words) {
2105
2096
  return words.map((word, idx) => idx == 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)).join('') }
2106
2097
 
2107
2098
  // Prefix console logs w/ '🤖 chatgpt.js >> '
2108
- const consolePrefix = '🤖 chatgpt.js >> ', ogError = console.error, ogInfo = console.info;
2099
+ const consolePrefix = '🤖 chatgpt.js >> ', ogError = console.error, ogInfo = console.info
2109
2100
  console.error = (...args) => {
2110
- if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2111
- else ogError(...args);
2112
- };
2101
+ if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1))
2102
+ else ogError(...args)
2103
+ }
2113
2104
  console.info = (msg) => {
2114
2105
  if (!msg.startsWith(consolePrefix)) ogInfo(consolePrefix + msg);
2115
- else ogInfo(msg);
2116
- };
2106
+ else ogInfo(msg)
2107
+ }
2117
2108
 
2118
2109
  // Export chatgpt object
2119
- try { window.chatgpt = chatgpt; } catch (err) {} // for Greasemonkey
2120
- try { module.exports = chatgpt; } catch (err) {} // for CommonJS
2110
+ try { window.chatgpt = chatgpt } catch (err) {} // for Greasemonkey
2111
+ try { module.exports = chatgpt } catch (err) {} // for CommonJS