-
Notifications
You must be signed in to change notification settings - Fork 122
/
chatgpt.js
2024 lines (1764 loc) · 110 KB
/
chatgpt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// © 2023–2024 KudoAI & contributors under the MIT license.
// Source: https://github.com/KudoAI/chatgpt.js
// User guide: https://chatgptjs.org/userguide
// Latest minified release: https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js/chatgpt.min.js
// Init feedback props
localStorage.alertQueue = JSON.stringify([]);
localStorage.notifyProps = JSON.stringify({ queue: { topRight: [], bottomRight: [], bottomLeft: [], topLeft: [] }});
// Define chatgpt API
const chatgpt = { // eslint-disable-line no-redeclare
openAIaccessToken: {}, endpoints: {
assets: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js',
openAI: {
session: 'https://chatgpt.com/api/auth/session',
chats: 'https://chatgpt.com/backend-api/conversations',
chat: 'https://chatgpt.com/backend-api/conversation',
share_create: 'https://chatgpt.com/backend-api/share/create',
share: 'https://chatgpt.com/backend-api/share',
instructions: 'https://chatgpt.com/backend-api/user_system_messages'
}},
actAs(persona) {
// Prompts ChatGPT to act as a persona from https://github.com/KudoAI/chat-prompts/blob/main/personas.json
const promptsUrl = 'https://raw.githubusercontent.com/KudoAI/chat-prompts/main/dist/personas.min.json';
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', promptsUrl, true); xhr.send();
xhr.onload = () => {
if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve prompts data.');
const data = JSON.parse(xhr.responseText).personas;
if (!persona) {
console.log('\n%c🤖 chatgpt.js personas\n',
'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold');
for (const prompt of data) // list personas
console.log(`%c${ prompt.title }`, 'font-family: monospace ; font-size: larger ;');
return resolve();
}
const selectedPrompt = data.find(obj => obj.title.toLowerCase() == persona.toLowerCase());
if (!selectedPrompt)
return reject(`🤖 chatgpt.js >> Persona '${ persona }' was not found!`);
chatgpt.send(selectedPrompt.prompt, 'click');
console.info(`Loading ${ persona } persona...`);
chatgpt.isIdle().then(() => { console.info('Persona activated!'); });
return resolve();
};
});
},
activateDarkMode() {
document.documentElement.classList.replace('light', 'dark');
document.documentElement.style.colorScheme = 'dark';
localStorage.setItem('theme', 'dark');
},
activateLightMode() {
document.documentElement.classList.replace('dark', 'light');
document.documentElement.style.colorScheme = 'light';
localStorage.setItem('theme', 'light');
},
alert(title, msg, btns, checkbox, width) {
// [ title/msg = strings, btns = [named functions], checkbox = named function, width (px) = int ] = optional
// * Spaces are inserted into button labels by parsing function names in camel/kebab/snake case
const scheme = chatgpt.isDarkMode() ? 'dark' : 'light',
isMobile = chatgpt.browser.isMobile();
// Create modal parent/children elements
const modalContainer = document.createElement('div');
modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
modalContainer.classList.add('chatgpt-modal'); // add class to main div
const modal = document.createElement('div'),
modalTitle = document.createElement('h2'),
modalMessage = document.createElement('p');
// Create/append/update modal style (if missing or outdated)
const thisUpdated = 20231203; // datestamp of last edit for this file's `modalStyle`
let modalStyle = document.querySelector('#chatgpt-modal-style'); // try to select existing style
if (!modalStyle || parseInt(modalStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
if (!modalStyle) { // outright missing, create/id/attr/append it first
modalStyle = document.createElement('style'); modalStyle.id = 'chatgpt-modal-style';
modalStyle.setAttribute('last-updated', thisUpdated.toString());
document.head.append(modalStyle);
}
modalStyle.innerText = ( // update prev/new style contents
'.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
// Background styles
+ '.chatgpt-modal {'
+ 'position: fixed ; top: 0 ; left: 0 ; width: 100% ; height: 100% ;' // expand to full view-port
+ 'background-color: rgba(67, 70, 72, 0) ;' // init dim bg but no opacity
+ 'transition: background-color 0.05s ease ;' // speed to transition in show alert routine
+ 'display: flex ; justify-content: center ; align-items: center ; z-index: 9999 }' // align
// Alert styles
+ '.chatgpt-modal > div {'
+ 'opacity: 0 ; transform: translateX(-2px) translateY(5px) ; max-width: 75vw ; word-wrap: break-word ;'
+ 'transition: opacity 0.1s cubic-bezier(.165,.84,.44,1), transform 0.2s cubic-bezier(.165,.84,.44,1) ;'
+ `background-color: ${ scheme == 'dark' ? 'black' : 'white' } ;`
+ ( scheme != 'dark' ? 'border: 1px solid rgba(0, 0, 0, 0.3) ;' : '' )
+ 'padding: 20px ; margin: 12px 23px ; border-radius: 15px ; box-shadow: 0 30px 60px rgba(0, 0, 0, .12) ;'
+ ' -webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ; }'
+ '.chatgpt-modal h2 { margin-bottom: 9px }'
+ `.chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}`
+ '.chatgpt-modal.animated > div { opacity: 1 ; transform: translateX(0) translateY(0) }'
+ '@keyframes alert-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }'
+ '50% { opacity: 0.25 ; transform: scale(1.05) }'
+ '100% { opacity: 0 ; transform: scale(1.35) }}'
// Button styles
+ '.modal-buttons { display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;'
+ ( isMobile ? 'flex-direction: column-reverse' : '' ) + '}'
+ '.chatgpt-modal button {'
+ `margin-left: ${ isMobile ? 0 : 10}px ; padding: ${ isMobile ? 15 : 4}px 18px ; border-radius: 15px ;`
+ ( isMobile ? 'margin-top: 5px ; margin-bottom: 3px ;' : '')
+ `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' }}`
+ '.primary-modal-btn {'
+ `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
+ `background: ${ scheme == 'dark' ? 'white' : 'black' } ;`
+ `color: ${ scheme == 'dark' ? 'black' : 'white' }}`
+ '.chatgpt-modal button:hover { color: #3d5d71 ; border-color: #6d9cb9 ;'
+ 'background-color: ' + ( scheme == 'dark' ? '#00cfff' : '#9cdaff' ) + ';'
+ 'box-shadow: 2px 1px ' + ( scheme == 'dark' ? '54px #00cfff' : '30px #9cdaff' ) + '}'
+ '.modal-close-btn {'
+ 'cursor: pointer ; width: 29px ; height: 29px ; border-radius: 17px ;'
+ 'float: right ; position: relative ; right: -6px ; top: -5px }'
+ '.modal-close-btn svg { margin: 10px }' // center SVG for hover underlay
+ `.modal-close-btn:hover { background-color: #f2f2f2${ scheme == 'dark' ? '00' : '' }}`
// Checkbox styles
+ '.chatgpt-modal .checkbox-group { display: flex ; margin-top: -18px }'
+ '.chatgpt-modal .checkbox-group label {'
+ 'font-size: .7rem ; margin: -.04rem 0 0px .3rem ;'
+ `color: ${ scheme == 'dark' ? '#e1e1e1' : '#1e1e1e' }}`
+ '.chatgpt-modal input[type="checkbox"] { transform: scale(0.7) ;'
+ `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' }}`
+ '.chatgpt-modal input[type="checkbox"]:checked {'
+ `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
+ 'background-color: black ; position: inherit }'
+ '.chatgpt-modal input[type="checkbox"]:focus { outline: none ; box-shadow: none }'
);
}
// Insert text into elements
modalTitle.innerText = title || '';
modalMessage.innerText = msg || ''; chatgpt.renderHTML(modalMessage);
// Create/append buttons (if provided) to buttons div
const modalButtons = document.createElement('div');
modalButtons.classList.add('modal-buttons', 'no-mobile-tap-outline');
if (btns) { // are supplied
if (!Array.isArray(btns)) btns = [btns]; // convert single button to array if necessary
btns.forEach((buttonFn) => { // create title-cased labels + attach listeners
const button = document.createElement('button');
button.textContent = buttonFn.name
.replace(/[_-]\w/g, match => match.slice(1).toUpperCase()) // convert snake/kebab to camel case
.replace(/([A-Z])/g, ' $1') // insert spaces
.replace(/^\w/, firstChar => firstChar.toUpperCase()); // capitalize first letter
button.onclick = () => { dismissAlert(); buttonFn(); };
modalButtons.insertBefore(button, modalButtons.firstChild); // insert button to left
});
}
// Create/append OK/dismiss button to buttons div
const dismissBtn = document.createElement('button');
dismissBtn.textContent = btns ? 'Dismiss' : 'OK';
modalButtons.insertBefore(dismissBtn, modalButtons.firstChild);
// Highlight primary button
modalButtons.lastChild.classList.add('primary-modal-btn');
// Create/append checkbox (if provided) to checkbox group div
const checkboxDiv = document.createElement('div');
if (checkbox) { // is supplied
checkboxDiv.classList.add('checkbox-group');
const checkboxFn = checkbox, // assign the named function to checkboxFn
checkboxInput = document.createElement('input');
checkboxInput.type = 'checkbox';
checkboxInput.onchange = checkboxFn;
// Create/show label
const checkboxLabel = document.createElement('label');
checkboxLabel.onclick = () => { checkboxInput.checked = !checkboxInput.checked; checkboxFn(); };
checkboxLabel.textContent = checkboxFn.name.charAt(0).toUpperCase() // capitalize first char
+ checkboxFn.name.slice(1) // format remaining chars
.replace(/([A-Z])/g, (match, letter) => ' ' + letter.toLowerCase()) // insert spaces, convert to lowercase
.replace(/\b(\w+)nt\b/gi, '$1n\'t') // insert apostrophe in 'nt' suffixes
.trim(); // trim leading/trailing spaces
checkboxDiv.append(checkboxInput); checkboxDiv.append(checkboxLabel);
}
// Create close button
const closeBtn = document.createElement('div');
closeBtn.title = 'Close'; closeBtn.classList.add('modal-close-btn', 'no-mobile-tap-outline');
const closeSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
closeSVG.setAttribute('height', '10px');
closeSVG.setAttribute('viewBox', '0 0 14 14');
closeSVG.setAttribute('fill', 'none');
const closeSVGpath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
closeSVGpath.setAttribute('fill-rule', 'evenodd');
closeSVGpath.setAttribute('clip-rule', 'evenodd');
closeSVGpath.setAttribute('fill', chatgpt.isDarkMode() ? 'white' : 'black');
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');
closeSVG.append(closeSVGpath); closeBtn.append(closeSVG);
// Assemble/append div
const modalElems = [closeBtn, modalTitle, modalMessage, modalButtons, checkboxDiv];
modalElems.forEach((elem) => { modal.append(elem); });
modal.style.width = `${ width || 458 }px`;
modalContainer.append(modal); document.body.append(modalContainer);
// Enqueue alert
let alertQueue = JSON.parse(localStorage.alertQueue);
alertQueue.push(modalContainer.id);
localStorage.alertQueue = JSON.stringify(alertQueue);
// Show alert if none active
modalContainer.style.display = 'none';
if (alertQueue.length === 1) {
modalContainer.style.display = '';
setTimeout(() => { // delay non-0 opacity's for transition fx
modalContainer.style.backgroundColor = (
`rgba(67, 70, 72, ${ scheme === 'dark' ? 0.62 : 0.1 })`);
modalContainer.classList.add('animated'); }, 100);
}
// Define click/key handlers
const clickHandler = event => { // explicitly defined to support removal post-dismissal
if (event.target == event.currentTarget || event.target instanceof SVGPathElement) dismissAlert(); };
const keyHandler = event => { // to dismiss active alert
const dismissKeys = [' ', 'Spacebar', 'Enter', 'Return', 'Escape', 'Esc'],
dismissKeyCodes = [32, 13, 27];
if (dismissKeys.includes(event.key) || dismissKeyCodes.includes(event.keyCode)) {
for (const alertId of alertQueue) { // look to handle only if triggering alert is active
const alert = document.getElementById(alertId);
if (alert && alert.style.display !== 'none') { // active alert found
if (event.key.includes('Esc') || event.keyCode == 27) // esc pressed
dismissAlert(); // dismiss alert & do nothing
else if ([' ', 'Spacebar', 'Enter', 'Return'].includes(event.key) || [32, 13].includes(event.keyCode)) { // space/enter pressed
const mainButton = alert.querySelector('.modal-buttons').lastChild; // look for main button
if (mainButton) { mainButton.click(); event.preventDefault(); } // click if found
} return;
}}}};
// Add listeners to dismiss alert
const dismissElems = [modalContainer, closeBtn, closeSVG, dismissBtn];
dismissElems.forEach(elem => elem.onclick = clickHandler);
document.addEventListener('keydown', keyHandler);
// Define alert dismisser
const dismissAlert = () => {
modalContainer.style.backgroundColor = 'transparent';
modal.style.animation = 'alert-zoom-fade-out 0.075s ease-out';
setTimeout(() => { // delay removal for fade-out
// Remove alert
modalContainer.remove(); // ...from DOM
alertQueue = JSON.parse(localStorage.alertQueue);
alertQueue.shift(); // + memory
localStorage.alertQueue = JSON.stringify(alertQueue); // + storage
document.removeEventListener('keydown', keyHandler); // prevent memory leaks
// Check for pending alerts in queue
if (alertQueue.length > 0) {
const nextAlert = document.getElementById(alertQueue[0]);
setTimeout(() => {
nextAlert.style.display = '';
setTimeout(() => { nextAlert.classList.add('animated'); }, 100);
}, 500);
}
}, 50);
};
return modalContainer.id; // if assignment used
},
async askAndGetReply(query) {
chatgpt.send(query); await chatgpt.isIdle();
return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
},
autoRefresh: {
activate(interval) {
if (this.isActive) { // already running, do nothing
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh already active!'); return; }
const autoRefresh = this;
// Run main activate routine
this.toggle.refreshFrame();
const scheduleRefreshes = interval => {
const randomDelay = Math.max(2, Math.floor(chatgpt.randomFloat() * 21 - 10)); // set random delay up to ±10 secs
autoRefresh.isActive = setTimeout(() => {
const manifestScript = document.querySelector('script[src*="_ssgManifest.js"]');
if (manifestScript) {
document.querySelector('#refresh-frame').src = manifestScript.src + '?' + Date.now();
console.log('↻ ChatGPT >> [' + autoRefresh.nowTimeStamp() + '] ChatGPT session refreshed');
}
scheduleRefreshes(interval);
}, (interval + randomDelay) * 1000);
};
scheduleRefreshes( interval ? parseInt(interval, 10) : 30 );
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh activated');
// Add listener to send beacons in Chromium to thwart auto-discards if Page Visibility API supported
if (navigator.userAgent.includes('Chrome') && typeof document.hidden !== 'undefined')
document.addEventListener('visibilitychange', this.toggle.beacons);
},
deactivate() {
if (this.isActive) {
this.toggle.refreshFrame();
document.removeEventListener('visibilitychange', this.toggle.beacons);
clearTimeout(this.isActive); this.isActive = null;
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh de-activated');
} else { console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Auto refresh already inactive!'); }
},
nowTimeStamp() {
const now = new Date();
const hours = now.getHours() % 12 || 12; // Convert to 12-hour format
let minutes = now.getMinutes(), seconds = now.getSeconds();
if (minutes < 10) minutes = '0' + minutes; if (seconds < 10) seconds = '0' + seconds;
const meridiem = now.getHours() < 12 ? 'AM' : 'PM';
return hours + ':' + minutes + ':' + seconds + ' ' + meridiem;
},
toggle: {
beacons() {
if (chatgpt.autoRefresh.beaconID) {
clearInterval(chatgpt.autoRefresh.beaconID); chatgpt.autoRefresh.beaconID = null;
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacons de-activated');
} else {
chatgpt.autoRefresh.beaconID = setInterval(() => {
navigator.sendBeacon('https://httpbin.org/post', new Uint8Array());
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacon sent');
}, 90000);
console.log('↻ ChatGPT >> [' + chatgpt.autoRefresh.nowTimeStamp() + '] Beacons activated');
}
},
refreshFrame() {
let refreshFrame = document.querySelector('#refresh-frame');
if (refreshFrame) refreshFrame.remove();
else {
refreshFrame = Object.assign(document.createElement('iframe'),
{ id: 'refresh-frame', style: 'display: none' });
document.head.prepend(refreshFrame);
}
}
}
},
browser: {
isLightMode() { return window.matchMedia?.('(prefers-color-scheme: light)')?.matches; },
isDarkMode() { return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches; },
isChromium() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chromium'); },
isChrome() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Chrome'); },
isEdge() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Edge'); },
isBrave() { return !!JSON.stringify(navigator.userAgentData?.brands)?.includes('Brave'); },
isFirefox() { return navigator.userAgent.includes('Firefox'); },
isFullScreen() {
const userAgentStr = navigator.userAgent;
return userAgentStr.includes('Chrome') ? window.matchMedia('(display-mode: fullscreen)').matches
: userAgentStr.includes('Firefox') ? window.fullScreen
: /MSIE|rv:/.test(userAgentStr) ? document.msFullscreenElement : document.webkitIsFullScreen;
},
isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); }
},
async clearChats() { // back-end method
return new Promise((resolve, reject) => {
chatgpt.getAccessToken().then(token => {
const xhr = new XMLHttpRequest();
xhr.open('PATCH', chatgpt.endpoints.openAI.chats, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.onload = () => {
if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot clear chats.');
console.info('Chats successfully cleared'); resolve();
};
xhr.send(JSON.stringify({ is_visible: false }));
}).catch(err => reject(new Error(err.message)));
});
},
code: {
// Tip: Use template literals for easier passing of code arguments. Ensure backticks and `$`s are escaped (using `\`)
async execute(code) {
if (!code) return console.error('Code argument not supplied. Pass some code!');
if (typeof code !== 'string') return console.error('Code argument must be a string!');
chatgpt.send('Display the output as if you were terminal:\n\n' + code);
console.info('Executing code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
},
extract(msg) { // extract pure code from response (targets last block)
const codeBlocks = msg.match(/(?<=```.*\n)[\s\S]*?(?=```)/g);
return codeBlocks ? codeBlocks[codeBlocks.length - 1] : msg;
},
async isIdle(timeout = null) {
const obsConfig = { childList: true, subtree: true },
selectors = { msgDiv: 'div[data-message-author-role]',
replyDiv: 'div[data-message-author-role="assistant"]' };
// Create promises
const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
const isIdlePromise = (async () => {
await new Promise(resolve => { // when on convo page
if (document.querySelector(selectors.msgDiv)) resolve();
else new MutationObserver((_, obs) => {
if (document.querySelector(selectors.msgDiv)) { obs.disconnect(); resolve(); }
}).observe(document.body, obsConfig);
});
await new Promise(resolve => { // when reply starts generating
new MutationObserver((_, obs) => {
if (chatgpt.getStopBtn()) { obs.disconnect(); resolve(); }
}).observe(document.body, { childList: true, subtree: true });
});
const replyDivs = document.querySelectorAll(selectors.replyDiv),
lastReplyDiv = replyDivs[replyDivs.length - 1];
await new Promise(resolve => { // when code starts generating
new MutationObserver((_, obs) => {
if (lastReplyDiv?.querySelector('pre')) { obs.disconnect(); resolve(); }
}).observe(document.body, obsConfig);
});
return new Promise(resolve => { // when code stops generating
new MutationObserver((_, obs) => {
if (lastReplyDiv?.querySelector('pre')?.nextElementSibling // code block not last child of reply div
|| !chatgpt.getStopBtn() // ...or reply outright stopped generating
) { obs.disconnect(); resolve(true); }
}).observe(document.body, obsConfig);
});
})();
return await (timeoutPromise ? Promise.race([isIdlePromise, timeoutPromise]) : isIdlePromise);
},
async minify(code) {
if (!code) return console.error('Code argument not supplied. Pass some code!');
if (typeof code !== 'string') return console.error('Code argument must be a string!');
chatgpt.send('Minify the following code:\n\n' + code);
console.info('Minifying code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
},
async obfuscate(code) {
if (!code) return console.error('Code argument not supplied. Pass some code!');
if (typeof code !== 'string') return console.error('Code argument must be a string!');
chatgpt.send('Obfuscate the following code:\n\n' + code);
console.info('Obfuscating code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
},
async refactor(code, objective) {
if (!code) return console.error('Code (1st) argument not supplied. Pass some code!');
for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
return console.error(`Argument ${ i + 1 } must be a string.`);
chatgpt.send('Refactor the following code for ' + (objective || 'brevity') + ':\n\n' + code);
console.info('Refactoring code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
},
async review(code) {
if (!code) return console.error('Code argument not supplied. Pass some code!');
if (typeof code !== 'string') return console.error('Code argument must be a string!');
chatgpt.send('Review the following code for me:\n\n' + code);
console.info('Reviewing code...');
await chatgpt.isIdle();
return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
},
async unminify(code) {
if (!code) return console.error('Code argument not supplied. Pass some code!');
if (typeof code !== 'string') return console.error('Code argument must be a string!');
chatgpt.send('Unminify the following code.:\n\n' + code);
console.info('Unminifying code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
},
async write(prompt, outputLang) {
if (!prompt) return console.error('Prompt (1st) argument not supplied. Pass a prompt!');
if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
return console.error(`Argument ${ i + 1 } must be a string.`);
chatgpt.send(prompt + '\n\nWrite this as code in ' + outputLang);
console.info('Writing code...');
await chatgpt.isIdle();
return chatgpt.code.extract(await chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'));
}
},
continue() { chatgpt.response.continue(); },
async detectLanguage(text) {
if (!text) return console.error('Text argument not supplied. Pass some text!');
if (typeof text !== 'string') return console.error('Text argument must be a string!');
chatgpt.send('Detect the language of the following text:\n\n' + text
+ '\n\nOnly respond with the name of the language');
console.info('Reviewing text...');
await chatgpt.isIdle();
return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest');
},
executeCode() { chatgpt.code.execute(); },
async exportChat(chatToGet, format) {
// chatToGet = 'active' (default) | 'latest' | index|title|id of chat to get
// format = 'html' (default) | 'md' | 'pdf' | 'text'
// Init args
chatToGet = !chatToGet ? 'active' // default to 'active' if unpassed
: Number.isInteger(chatToGet) || /^\d+$/.test(chatToGet) ? // else if string/int num passed
parseInt(chatToGet, 10) // parse as integer
: chatToGet; // else preserve non-num string as 'active', 'latest' or title/id of chat to get
format = format.toLowerCase() || 'html'; // default to 'html' if unpassed
// Create transcript + filename
console.info('Generating transcript...');
let transcript = '', filename;
if (/te?xt/.test(format)) { // generate plain transcript + filename for TXT export
// Format filename using date/time
const now = new Date(),
day = now.getDate().toString().padStart(2, '0'),
month = (now.getMonth() + 1).toString().padStart(2, '0'),
year = now.getFullYear(),
hour = now.getHours().toString().padStart(2, '0'),
minute = now.getMinutes().toString().padStart(2, '0');
filename = `ChatGPT_${ day }-${ month }-${ year }_${ hour }-${ minute }.txt`;
// Create transcript from active chat
if (chatToGet == 'active' && /\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(window.location.href)) {
const chatDivs = document.querySelectorAll('main > div > div > div > div > div > div[class*=group]');
if (chatDivs.length === 0) return console.error('Chat is empty!');
const msgs = []; let isUserMsg = true;
chatDivs.forEach((div) => {
const sender = isUserMsg ? 'USER' : 'CHATGPT'; isUserMsg = !isUserMsg;
const msg = Array.from(div.childNodes).map(node => node.innerText)
.join('\n\n') // insert double line breaks between paragraphs
.replace('Copy code', '');
msgs.push(sender + ': ' + msg);
});
transcript = msgs.join('\n\n');
// ...or from getChatData(chatToGet)
} else {
for (const entry of await chatgpt.getChatData(chatToGet, 'msg', 'both', 'all')) {
transcript += `USER: ${ entry.user }\n\n`;
transcript += `CHATGPT: ${ entry.chatgpt }\n\n`;
}}
} else { // generate rich transcript + filename for HTML/MD/PDF export
// Fetch HTML transcript from OpenAI
const response = await fetch(await chatgpt.shareChat(chatToGet)),
htmlContent = await response.text();
// Format filename after <title>
const parser = new DOMParser(),
parsedHtml = parser.parseFromString(htmlContent, 'text/html');
filename = `${ parsedHtml.querySelector('title').textContent || 'ChatGPT conversation' }.html`;
// Convert relative CSS paths to absolute ones
const cssLinks = parsedHtml.querySelectorAll('link[rel="stylesheet"]');
cssLinks.forEach(link => {
const href = link.getAttribute('href');
if (href?.startsWith('/')) link.setAttribute('href', 'https://chat.openai.com' + href);
});
// Serialize updated HTML to string
transcript = new XMLSerializer().serializeToString(parsedHtml);
}
// Export transcript
console.info(`Exporting transcript as ${ format.toUpperCase() }...`);
if (format == 'pdf') { // convert SVGs + launch PDF printer
// Convert SVG icons to data URLs for proper PDF rendering
transcript = transcript.replace(/<svg.*?<\/svg>/g, (match) => {
const dataURL = 'data:image/svg+xml,' + encodeURIComponent(match);
return `<img src="${ dataURL }">`;
});
// Launch PDF printer
const transcriptPopup = window.open('', '', 'toolbar=0, location=0, menubar=0, height=600, width=800');
transcriptPopup.document.write(transcript);
setTimeout(() => { transcriptPopup.print({ toPDF: true }); }, 100);
} else { // auto-save to file
if (format == 'md') { // remove extraneous HTML + fix file extension
const mdMatch = /<.*(?:<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?)<[^/]/.exec(transcript)[1];
transcript = mdMatch || transcript; filename = filename.replace('.html', '.md');
}
const blob = new Blob([transcript],
{ type: 'text/' + ( format == 'html' ? 'html' : format == 'md' ? 'markdown' : 'plain' )});
const link = document.createElement('a'), blobURL = URL.createObjectURL(blob);
link.href = blobURL; link.download = filename; document.body.append(link);
link.click(); document.body.removeChild(link); URL.revokeObjectURL(blobURL);
}
},
extractCode() { chatgpt.code.extract(); },
focusChatbar() { chatgpt.getChatBox()?.focus(); },
footer: {
get() { return document.querySelector('main form')?.parentNode.parentNode.nextElementSibling; },
hide() {
const footer = chatgpt.footer.get();
if (!footer) return console.error('Footer element not found!');
if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!');
footer.style.visibility = 'hidden'; footer.style.height = '3px';
},
show() {
const footer = chatgpt.footer.get();
if (!footer) return console.error('Footer element not found!');
if (footer.style.visibility != 'hidden') return console.info('Footer already shown!');
footer.style.visibility = footer.style.height = 'inherit';
}
},
generateRandomIP() {
const ip = Array.from({length: 4}, () => Math.floor(chatgpt.randomFloat() * 256)).join('.');
console.info('IP generated: ' + ip);
return ip;
},
get(targetType, targetName = '') {
// targetType = 'button'|'link'|'div'|'response'
// targetName = from get[targetName][targetType] methods, e.g. 'send'
// Validate argument types to be string only
if (typeof targetType !== 'string' || typeof targetName !== 'string') {
throw new TypeError('Invalid arguments. Both arguments must be strings.'); }
// Validate targetType
if (!cjsTargetTypes.includes(targetType.toLowerCase())) {
throw new Error('Invalid targetType: ' + targetType
+ '. Valid values are: ' + JSON.stringify(cjsTargetTypes)); }
// Validate targetName scoped to pre-validated targetType
const targetNames = [], reTargetName = new RegExp('^get(.*)' + targetType + '$', 'i');
for (const prop in chatgpt) {
if (typeof chatgpt[prop] == 'function' && reTargetName.test(prop)) {
targetNames.push( // add found targetName to valid array
prop.replace(reTargetName, '$1').toLowerCase());
}}
if (!targetNames.includes(targetName.toLowerCase())) {
throw new Error('Invalid targetName: ' + targetName + '. '
+ (targetNames.length > 0 ? 'Valid values are: ' + JSON.stringify(targetNames)
: 'targetType ' + targetType.toLowerCase() + ' does not require additional options.'));
}
// Call target function using pre-validated name components
const targetFuncNameLower = ('get' + targetName + targetType).toLowerCase();
const targetFuncName = Object.keys(this).find( // find originally cased target function name
(name) => { return name.toLowerCase() == targetFuncNameLower; }); // test for match
return this[targetFuncName](); // call found function
},
getAccessToken() {
return new Promise((resolve, reject) => {
if (Object.keys(chatgpt.openAIaccessToken).length > 0 && // populated
(Date.parse(chatgpt.openAIaccessToken.expireDate) - Date.parse(new Date()) >= 0)) // not expired
return resolve(chatgpt.openAIaccessToken.token);
const xhr = new XMLHttpRequest();
xhr.open('GET', chatgpt.endpoints.openAI.session, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve access token.');
console.info('Token expiration: ' + new Date(JSON.parse(xhr.responseText).expires).toLocaleString().replace(',', ' at'));
chatgpt.openAIaccessToken = {
token: JSON.parse(xhr.responseText).accessToken,
expireDate: JSON.parse(xhr.responseText).expires
};
return resolve(chatgpt.openAIaccessToken.token);
};
xhr.send();
});
},
getAccountDetails(...details) {
// details = [email|id|image|name|picture] = optional
// Build details array
const validDetails = [ 'email', 'id', 'image', 'name', 'picture' ];
details = ( !arguments[0] ? validDetails // no details passed, populate w/ all valid ones
: Array.isArray(arguments[0]) ? arguments[0] // details array passed, do nothing
: Array.from(arguments) ); // details arg(s) passed, convert to array
// Validate detail args
for (const detail of details) {
if (!validDetails.includes(detail)) { return console.error(
'Invalid detail arg \'' + detail + '\' supplied. Valid details are:\n'
+ ' [' + validDetails + ']'); }}
// Return account details
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', chatgpt.endpoints.openAI.session, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText).user, detailsToReturn = {};
for (const detail of details) detailsToReturn[detail] = data[detail];
return resolve(detailsToReturn);
} else return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve account details.');
};
xhr.send();
});
},
getChatBox() { return document.getElementById('prompt-textarea'); },
getChatData(chatToGet = 1, detailsToGet = 'all', sender = 'all', msgToGet = 'all') {
// chatToGet = 'active' | 'latest' | index|title|id of chat to get (defaults to active OpenAI chat > latest chat)
// detailsToGet = 'all' | [ 'id' | 'title' | 'create_time' | 'update_time' | 'msg' ] (defaults to 'all', excludes msg's)
// sender = ( 'all' | 'both' ) | 'user' | 'chatgpt' (defaults to 'all', requires 2nd param = 'msg')
// msgToGet = 'all' | 'latest' | index of msg to get (defaults to 'all', requires 2nd param = 'msg')
// Init args
const validDetails = [ 'all', 'id', 'title', 'create_time', 'update_time', 'msg' ];
const validSenders = [ 'all', 'both', 'user', 'chatgpt' ];
chatToGet = !chatToGet ? 'active' // if '' passed, set to active
: Number.isInteger(chatToGet) || /^\d+$/.test(chatToGet) ? // else if string/int num passed
( parseInt(chatToGet, 10) === 0 ? 0 : parseInt(chatToGet, 10) - 1 ) // ...offset -1 or keep as 0
: chatToGet; // else preserve non-num string as 'active', 'latest' or title/id of chat to get
detailsToGet = ['all', ''].includes(detailsToGet) ? // if '' or 'all' passed
validDetails.filter(detail => /^(?!all$|msg$).*/.test(detail)) // populate w/ [validDetails] except 'all' & 'msg'
: Array.isArray(detailsToGet) ? detailsToGet : [detailsToGet]; // else convert to array if needed
sender = !sender ? 'all' // if '' or unpassed, set to 'all'
: validSenders.includes(sender) ? sender : 'invalid'; // else set to validSenders or 'invalid'
msgToGet = Number.isInteger(msgToGet) || /^\d+$/.test(msgToGet) ? // if string/int num passed
( parseInt(msgToGet, 10) === 0 ? 0 : parseInt(msgToGet, 10) - 1 ) // ...offset -1 or keep as 0
: ['all', 'latest'].includes(msgToGet.toLowerCase()) ? // else if 'all' or 'latest' passed
msgToGet.toLowerCase() // ...preserve it
: !msgToGet ? 'all' // else if '', set to 'all'
: 'invalid'; // else set 'invalid' for validation step
// Validate args
for (const detail of detailsToGet) {
if (!validDetails.includes(detail)) { return console.error(
'Invalid detail arg \'' + detail + '\' passed. Valid details are:\n'
+ ' [' + validDetails + ']'); }}
if (sender == 'invalid') { return console.error(
'Invalid sender arg passed. Valid senders are:\n'
+ ' [' + validSenders + ']'); }
if (msgToGet == 'invalid') { return console.error(
'Invalid msgToGet arg passed. Valid msg\'s to get are:\n'
+ ' [ \'all\' | \'latest\' | index of msg to get ]'); }
const getChatDetails = (token, detailsToGet) => {
const re_chatID = /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', chatgpt.endpoints.openAI.chats, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.onload = () => {
if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat details.');
const data = JSON.parse(xhr.responseText).items;
if (data.length <= 0) return reject('🤖 chatgpt.js >> Chat list is empty.');
const detailsToReturn = {};
// Return by index if num, 'latest', or 'active' passed but not truly active
if (Number.isInteger(chatToGet) || chatToGet == 'latest' ||
(chatToGet == 'active' && !new RegExp('\/' + re_chatID.source + '$').test(window.location.href))) {
chatToGet = Number.isInteger(chatToGet) ? chatToGet : 0; // preserve index, otherwise get latest
if (chatToGet > data.length) { // reject if index out-of-bounds
return reject('🤖 chatgpt.js >> Chat with index ' + ( chatToGet + 1 )
+ ' is out of bounds. Only ' + data.length + ' chats exist!'); }
for (const detail of detailsToGet) detailsToReturn[detail] = data[chatToGet][detail];
return resolve(detailsToReturn);
}
// Return by title, ID or active chat
const chatIdentifier = ( // determine to check by ID or title
chatToGet == 'active' || new RegExp('^' + re_chatID.source + '$').test(chatToGet) ? 'id' : 'title' );
if (chatToGet == 'active') // replace chatToGet w/ actual ID
chatToGet = re_chatID.exec(window.location.href)[0];
let idx, chatFound; // index of potentially found chat, flag if found
for (idx = 0; idx < data.length; idx++) { // search for id/title to set chatFound flag
if (data[idx][chatIdentifier] == chatToGet) { chatFound = true; break; }}
if (!chatFound) // exit
return reject('🤖 chatgpt.js >> No chat with ' + chatIdentifier + ' = ' + chatToGet + ' found.');
for (const detail of detailsToGet) detailsToReturn[detail] = data[idx][detail];
return resolve(detailsToReturn);
};
xhr.send();
});};
const getChatMsgs = token => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
getChatDetails(token, ['id']).then(chat => {
xhr.open('GET', `${chatgpt.endpoints.openAI.chat}/${chat.id}`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.onload = () => {
if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve chat messages.');
// Init const's
const data = JSON.parse(xhr.responseText).mapping; // Get chat messages
const userMessages = [], chatGPTMessages = [], msgsToReturn = [];
// Fill [userMessages]
for (const key in data)
if (data[key].message != null && data[key].message.author.role == 'user')
userMessages.push({ id: data[key].id, msg: data[key].message });
userMessages.sort((a, b) => a.msg.create_time - b.msg.create_time); // sort in chronological order
if (parseInt(msgToGet, 10) + 1 > userMessages.length) // reject if index out of bounds
return reject('🤖 chatgpt.js >> Message/response with index ' + ( msgToGet + 1)
+ ' is out of bounds. Only ' + userMessages.length + ' messages/responses exist!');
// Fill [chatGPTMessages]
for (const userMessage of userMessages) {
let sub = [];
for (const key in data) {
if (data[key].message != null && data[key].message.author.role == 'assistant' && data[key].parent == userMessage.id) {
sub.push(data[key].message);
}
}
sub.sort((a, b) => a.create_time - b.create_time); // sort in chronological order
sub = sub.map(x => { // pull out msgs after sorting
switch(x.content.content_type) {
case 'code': return x.content.text;
case 'text': return x.content.parts[0];
default: return;
}
});
sub = sub.length === 1 ? sub[0] : sub; // convert not regenerated responses to strings
chatGPTMessages.push(sub); // array of arrays (length > 1 = regenerated responses)
}
if (sender == 'user') // Fill [msgsToReturn] with user messages
for (const userMessage in userMessages)
msgsToReturn.push(userMessages[userMessage].msg.content.parts[0]);
else if (sender == 'chatgpt') // Fill [msgsToReturn] with ChatGPT responses
for (const chatGPTMessage of chatGPTMessages)
msgsToReturn.push(msgToGet == 'latest' ? chatGPTMessages[chatGPTMessages.length - 1] : chatGPTMessage );
else { // Fill [msgsToReturn] with objects of user messages and chatgpt response(s)
let i = 0;
for (const message in userMessages) {
msgsToReturn.push({
user: userMessages[message].msg.content.parts[0],
chatgpt: msgToGet == 'latest' ? chatGPTMessages[i][chatGPTMessages[i].length - 1] : chatGPTMessages[i]
});
i++;
}
}
return resolve(msgToGet == 'all' ? msgsToReturn // if 'all' passed, return array
: msgToGet == 'latest' ? msgsToReturn[msgsToReturn.length - 1] // else if 'latest' passed, return latest
: msgsToReturn[msgToGet] ); // else return element of array
};
xhr.send();
});});};
// Return chat data
return new Promise(resolve => chatgpt.getAccessToken().then(token => {
return resolve(detailsToGet.includes('msg') ? getChatMsgs(token)
: getChatDetails(token, detailsToGet));
}));
},
getChatInput() { return chatgpt.getChatBox().firstChild.innerText; },
getContinueButton() { return document.querySelector('button:has([d^="M4.47189"])'); },
getFooterDiv() { return chatgpt.footer.get(); },
getHeaderDiv() { return chatgpt.header.get(); },
getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest'); },
getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
getNewChatButton() { return document.querySelector('button[data-testid*="new-chat-button"]'); },
getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
getResponse() {
// * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
// chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
// responseToGet = index of response to get (defaults to latest if '' unpassed)
// regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
return chatgpt.response.get(...arguments);
},
getResponseFromAPI(chatToGet, responseToGet) { return chatgpt.response.getFromAPI(chatToGet, responseToGet); },
getResponseFromDOM(pos) { return chatgpt.response.getFromDOM(pos); },
getScrollToBottomButton() { return document.querySelector('button:has([d^="M12 21C11.7348"])'); },
getSendButton() { return document.querySelector('[data-testid="send-button"]'); },
getStopButton() { return document.querySelector('button[data-testid="stop-button"]'); },
getUserLanguage() {
return navigator.languages[0] || navigator.language || navigator.browserLanguage ||
navigator.systemLanguage || navigator.userLanguage || '';
},
header: {
get() { return document.querySelector('main .sticky'); },
hide() { chatgpt.header.get().style.display = 'none'; },
show() { chatgpt.header.get().style.display = 'flex'; }
},
hideFooter() { chatgpt.footer.hide(); },
hideHeader() { chatgpt.header.hide(); },
history: {
async isLoaded(timeout = null) {
const timeoutPromise = timeout ? new Promise(resolve => setTimeout(() => resolve(false), timeout)) : null;
const isLoadedPromise = new Promise(resolve => {
if (document.querySelector('nav')) resolve(true);
else new MutationObserver((_, obs) => {
if (document.querySelector('nav')) { obs.disconnect(); resolve(true); }
}).observe(document.body, { childList: true, subtree: true });
});
return await ( timeoutPromise ? Promise.race([isLoadedPromise, timeoutPromise]) : isLoadedPromise );
}
},
instructions: {
// NOTE: DOM is not updated to reflect new instructions added/removed or toggle state (until session refresh)
add(instruction, target) {
if (!instruction) return console.error('Please provide an instruction');
if (typeof instruction !== 'string') return console.error('Instruction must be a string');
const validTargets = ['user', 'chatgpt']; // valid targets
if (!target) return console.error('Please provide a valid target!');
if (typeof target !== 'string') return console.error('Target must be a string');
target = target.toLowerCase(); // lowercase target
if (!validTargets.includes(target))
return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`);
instruction = `\n\n${instruction}`; // add 2 newlines to the new instruction
return new Promise(resolve => {
chatgpt.getAccessToken().then(async token => {
const instructionsData = await this.fetchData();
// Concatenate old instructions with new instruction
if (target == 'user') instructionsData.about_user_message += instruction;
else if (target == 'chatgpt') instructionsData.about_model_message += instruction;
await this.sendRequest('POST', token, instructionsData);
return resolve();
});
});
},
clear(target) {
const validTargets = ['user', 'chatgpt']; // valid targets
if (!target) return console.error('Please provide a valid target!');
if (typeof target !== 'string') return console.error('Target must be a string');
target = target.toLowerCase(); // lowercase target
if (!validTargets.includes(target))
return console.error(`Invalid target ${target}. Valid targets are [${validTargets}]`);
return new Promise(resolve => {
chatgpt.getAccessToken().then(async token => {
const instructionsData = await this.fetchData();
// Clear target's instructions
if (target == 'user') instructionsData.about_user_message = '';
else if (target == 'chatgpt') instructionsData.about_model_message = '';
await this.sendRequest('POST', token, instructionsData);
return resolve();
});});
},
fetchData() {
// INTERNAL METHOD
return new Promise(resolve => {
chatgpt.getAccessToken().then(async token => {
return resolve(await this.sendRequest('GET', token)); // Return API data
});});
},
sendRequest(method, token, body) {
// INTERNAL METHOD
// Validate args
for (let i = 0; i < arguments.length - 1; i++) if (typeof arguments[i] !== 'string')
return console.error(`Argument ${ i + 1 } must be a string`);
const validMethods = ['POST', 'GET'];