@kudoai/chatgpt.js 3.6.2 → 3.7.0

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