@kudoai/chatgpt.js 2.6.7 → 2.6.8

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