@kudoai/chatgpt.js 3.6.3 → 3.7.1

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