@kudoai/chatgpt.js 3.3.4 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +35 -22
  2. package/chatgpt.js +130 -79
  3. package/dist/chatgpt.min.js +3 -3
  4. package/docs/README.md +35 -22
  5. package/docs/SECURITY.md +6 -1
  6. package/docs/USERGUIDE.md +4 -4
  7. package/package.json +18 -11
  8. package/starters/chrome/docs/README.md +13 -9
  9. package/starters/chrome/extension/components/icons.js +32 -0
  10. package/starters/chrome/extension/components/modals.js +121 -0
  11. package/starters/chrome/extension/content.js +121 -47
  12. package/starters/chrome/extension/icons/faded/icon128.png +0 -0
  13. package/starters/chrome/extension/icons/faded/icon16.png +0 -0
  14. package/starters/chrome/extension/icons/faded/icon32.png +0 -0
  15. package/starters/chrome/extension/icons/faded/icon64.png +0 -0
  16. package/starters/chrome/extension/lib/chatgpt.js +130 -79
  17. package/starters/chrome/extension/lib/dom.js +28 -0
  18. package/starters/chrome/extension/lib/settings.js +30 -0
  19. package/starters/chrome/extension/manifest.json +25 -22
  20. package/starters/chrome/extension/popup/controller.js +138 -0
  21. package/starters/chrome/extension/popup/index.html +7 -44
  22. package/starters/chrome/extension/popup/style.css +15 -6
  23. package/starters/chrome/extension/service-worker.js +38 -0
  24. package/starters/docs/README.md +9 -2
  25. package/starters/greasemonkey/chatgpt.js-greasemonkey-starter.user.js +12 -12
  26. package/starters/greasemonkey/docs/README.md +2 -0
  27. package/starters/chrome/extension/background.js +0 -14
  28. package/starters/chrome/extension/lib/settings-utils.js +0 -24
  29. package/starters/chrome/extension/popup/popup.js +0 -92
  30. package/starters/chrome/media/images/icons/refresh/icon16.png +0 -0
  31. package/starters/chrome/media/images/icons/refresh/icon50.png +0 -0
  32. /package/starters/chrome/{media/images → images}/icons/question-mark/icon16.png +0 -0
  33. /package/starters/chrome/{media/images → images}/icons/question-mark/icon512.png +0 -0
  34. /package/starters/chrome/{media/images → images}/screenshots/chatgpt-extension-in-list.png +0 -0
  35. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.png +0 -0
  36. /package/starters/chrome/{media/images → images}/screenshots/developer-mode-toggle.psd +0 -0
  37. /package/starters/chrome/{media/images → images}/screenshots/extension-loaded.png +0 -0
  38. /package/starters/chrome/{media/images → images}/screenshots/load-unpacked-button.png +0 -0
  39. /package/starters/chrome/{media/images → images}/screenshots/reload-extension-button.png +0 -0
  40. /package/starters/chrome/{media/images → images}/screenshots/reload-page-button.png +0 -0
  41. /package/starters/chrome/{media/images → images}/screenshots/select-extension-folder.png +0 -0
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  <div id="repo-cover" align="center">
2
2
 
3
+ <a id="top"></a>
4
+
3
5
  <div align="center">
4
6
  <h6>
5
7
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs">
@@ -14,13 +16,14 @@
14
16
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/ja#readme">日本</a> |
15
17
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/ko#readme">한국인</a> |
16
18
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/hi#readme">हिंदी</a> |
17
- <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/np#readme">नेपाली</a> |
19
+ <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/ne#readme">नेपाली</a> |
18
20
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/de#readme">Deutsch</a> |
19
21
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/es#readme">Español</a> |
20
22
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/fr#readme">Français</a> |
21
23
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/it#readme">Italiano</a> |
22
24
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/nl#readme">Nederlands</a> |
23
25
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/pt#readme">Português</a> |
26
+ <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/ru#readme">Английский</a> |
24
27
  <a href="https://github.com/KudoAI/chatgpt.js/tree/main/docs/vi#readme">Việt</a>
25
28
  </h6>
26
29
  </div>
@@ -44,7 +47,7 @@
44
47
 
45
48
  [![](https://img.shields.io/github/stars/KudoAI/chatgpt.js?label=Stars&color=af68ff&logo=github&logoColor=white&labelColor=464646&style=for-the-badge)](https://github.com/KudoAI/chatgpt.js/stargazers)
46
49
  [![](https://img.shields.io/badge/License-MIT-green.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge)](https://github.com/KudoAI/chatgpt.js/blob/main/LICENSE.md)
47
- [![](https://img.shields.io/github/size/KudoAI/chatgpt.js/dist/chatgpt.min.js?branch=v3.3.4&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge)](https://github.com/KudoAI/chatgpt.js/tree/v3.3.4/dist/chatgpt.min.js)
50
+ [![](https://img.shields.io/github/size/KudoAI/chatgpt.js/dist/chatgpt.min.js?branch=v3.4.0&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge)](https://github.com/KudoAI/chatgpt.js/tree/v3.4.0/dist/chatgpt.min.js)
48
51
  [![](https://img.shields.io/codefactor/grade/github/kudoai/chatgpt.js?label=Code+Quality&logo=codefactor&logoColor=white&labelColor=464646&color=1acc6c&style=for-the-badge)](https://www.codefactor.io/repository/github/kudoai/chatgpt.js)
49
52
  [![](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dkudoai_chatgpt.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold)](https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=kudoai_chatgpt.js)
50
53
  [![](https://img.shields.io/badge/Mentioned_in-Awesome-cca8c4?logo=awesomelists&logoColor=white&labelColor=464646&style=for-the-badge)](https://github.com/sindresorhus/awesome-chatgpt#javascript)
@@ -76,13 +79,14 @@
76
79
 
77
80
  </div>
78
81
 
79
- > **Note** _To always import the latest version (not recommended in production!) replace the versioned jsDelivr URL with: `https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js/chatgpt.min.js`_
82
+ > [!NOTE]
83
+ > To always import the latest version (not recommended in production!) replace the versioned jsDelivr URL with: `https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js/chatgpt.min.js`
80
84
 
81
85
  ### ES11 (2020):
82
86
 
83
87
  ```js
84
88
  (async () => {
85
- await import('https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.3.4/dist/chatgpt.min.js');
89
+ await import('https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.4.0/dist/chatgpt.min.js');
86
90
  // Your code here...
87
91
  })();
88
92
  ```
@@ -91,7 +95,7 @@
91
95
 
92
96
  ```js
93
97
  var xhr = new XMLHttpRequest();
94
- xhr.open('GET', 'https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.3.4/dist/chatgpt.min.js');
98
+ xhr.open('GET', 'https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.4.0/dist/chatgpt.min.js');
95
99
  xhr.onload = function () {
96
100
  if (xhr.status === 200) {
97
101
  var chatgptJS = document.createElement('script');
@@ -109,11 +113,12 @@ function yourCode() {
109
113
 
110
114
  ### <img style="margin: 0 2px -0.065rem 0" height=17 src="https://media.chatgptjs.org/images/icons/platforms/tampermonkey/icon28.png?a3e53bf7"><img style="margin: 0 2px -0.035rem 1px" height=17.5 src="https://media.chatgptjs.org/images/icons/platforms/violentmonkey/icon25.png?a3e53bf7"> Greasemonkey:
111
115
 
112
- > **Note** _To use a starter template: [kudoai/chatgpt.js-greasemonkey-starter](https://github.com/KudoAI/chatgpt.js-greasemonkey-starter)_
116
+ > [!NOTE]
117
+ > To use a starter template: [kudoai/chatgpt.js-greasemonkey-starter](https://github.com/KudoAI/chatgpt.js-greasemonkey-starter)
113
118
 
114
119
  ```js
115
120
  ...
116
- // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.3.4/dist/chatgpt.min.js
121
+ // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.4.0/dist/chatgpt.min.js
117
122
  // ==/UserScript==
118
123
 
119
124
  // Your code here...
@@ -121,7 +126,8 @@ function yourCode() {
121
126
 
122
127
  ### <img style="margin: 0 2px -1px 0" height=16 src="https://media.chatgptjs.org/images/icons/platforms/chrome/icon16.png?8c852fa5"> Chrome:
123
128
 
124
- > **Note** _To use a starter template: [kudoai/chatgpt.js-chrome-starter](https://github.com/KudoAI/chatgpt.js-chrome-starter)_
129
+ > [!NOTE]
130
+ > To use a starter template: [kudoai/chatgpt.js-chrome-starter](https://github.com/KudoAI/chatgpt.js-chrome-starter)
125
131
 
126
132
  Since Google does not allow remote code, importing chatgpt.js locally is required:
127
133
 
@@ -182,7 +188,7 @@ chatgpt.get('reply', 'last');
182
188
 
183
189
  Each call equally fetches the last response. If you think it works, it probably will... so just type it!
184
190
 
185
- If it didn't, check out the extended [userguide](https://github.com/KudoAI/chatgpt.js/blob/v3.3.4/docs/USERGUIDE.md), or simply submit an [issue](https://github.com/KudoAI/chatgpt.js/issues) or [PR](https://github.com/KudoAI/chatgpt.js/pulls) and it will be integrated, ezpz!
191
+ If it didn't, check out the extended [userguide](https://github.com/KudoAI/chatgpt.js/blob/v3.4.0/docs/USERGUIDE.md), or simply submit an [issue](https://github.com/KudoAI/chatgpt.js/issues) or [PR](https://github.com/KudoAI/chatgpt.js/pulls) and it will be integrated, ezpz!
186
192
 
187
193
  <img height=8px width="100%" src="https://media.chatgptjs.org/images/separators/gradient-aqua.png?78210a7">
188
194
 
@@ -321,6 +327,15 @@ This library exists thanks to code, translations, issues & ideas from the follow
321
327
  [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/155944537?first-contrib=2024.8.27-sidebar-update-testing&h=47&w=47&mask=circle&maxage=7d "@svan-b")](https://github.com/svan-b)
322
328
  [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/74002352?first-contrib=2024.8.28-sidebar-update-testing&h=47&w=47&mask=circle&maxage=7d "@Jeff-Zzh")](https://github.com/Jeff-Zzh)
323
329
  [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/12472719?first-contrib=2024.10.2-getchatinput-stopped-working-report&h=47&w=47&mask=circle&maxage=7d "@ae3e")](https://github.com/ae3e)
330
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/129654632?first-contrib=2024.10.10-userguide-typo-correction&h=47&w=47&mask=circle&maxage=7d "@FarukhS52")](https://github.com/FarukhS52)
331
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/183274513?first-contrib=2024.10.10-create-ru-readme&h=47&w=47&mask=circle&maxage=7d "@Innovatorcloudy")](https://github.com/Innovatorcloudy)
332
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/94866865?first-contrib=2024.10.11-fix-readme-back-to-top-link&h=47&w=47&mask=circle&maxage=7d "@barbarian360")](https://github.com/barbarian360)
333
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/98452243?first-contrib=2024.10.26-fix-nepali-doc-link&h=47&w=47&mask=circle&maxage=7d "@adityadeshpande09")](https://github.com/adityadeshpande09)
334
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/46562212?first-contrib=2024.10.27-added-callout-notation-to-en-readme&h=47&w=47&mask=circle&maxage=7d "@twlite")](https://github.com/twlite)
335
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/100464898?first-contrib=2024.10.27-fix-nepali-grammar&h=47&w=47&mask=circle&maxage=7d "@sulav7")](https://github.com/sulav7)
336
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/75193555?first-contrib=2024.10.28-fix-nepali-typo&h=47&w=47&mask=circle&maxage=7d "@samir-byte")](https://github.com/samir-byte)
337
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/54546340?first-contrib=2024.10.28-fix-nepali-grammar&h=47&w=47&mask=circle&maxage=7d "@ghimirebibek")](https://github.com/ghimirebibek)
338
+ [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/82641474?first-contrib=2024.10.30-improve-hindi-readme&h=47&w=47&mask=circle&maxage=7d "@JanumalaAkhilendra")](https://github.com/JanumalaAkhilendra)
324
339
  [![](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/in/29110&h=47&w=47&mask=circle&maxage=7d "Dependabot")](https://github.com/dependabot)
325
340
  <a href="https://chatgpt.com"><picture><source type="image/png" media="(prefers-color-scheme: dark)" srcset="https://images.weserv.nl/?url=https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@main/media/images/icons/platforms/chatgpt/black-on-white/icon189.png?h=46&w=46&mask=circle&maxage=7d"><img src="https://images.weserv.nl/?url=https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@main/media/images/icons/platforms/chatgpt/white-on-black/icon189.png?h=46&w=46&mask=circle&maxage=7d" title="ChatGPT"></picture></a>
326
341
  <a href="https://poe.com"><picture><source type="image/png" media="(prefers-color-scheme: dark)" srcset="https://images.weserv.nl/?url=https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@main/media/images/icons/platforms/poe/w-purple-blue-stripes/black-on-white/icon175.png?h=46&w=46&mask=circle&maxage=7d"><img src="https://images.weserv.nl/?url=https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@main/media/images/icons/platforms/poe/w-purple-blue-stripes/white-on-black/icon175.png?h=46&w=46&mask=circle&maxage=7d" title="Poe"></picture></a>
@@ -330,20 +345,18 @@ This library exists thanks to code, translations, issues & ideas from the follow
330
345
 
331
346
  <img height=8px width="100%" src="https://media.chatgptjs.org/images/separators/gradient-aqua.png?78210a7">
332
347
 
333
- <div id="partners">
334
-
335
- ## 🤝 Partners
336
-
337
- </div>
348
+ <div align="center">
338
349
 
339
- **chatgpt.js** is funded in part by:
350
+ <br>
340
351
 
341
- <div id="partners-collage" align="center">
352
+ **chatgpt.js** is funded in part by:
342
353
 
343
- <picture>
344
- <source type="image/png" media="(prefers-color-scheme: dark)" srcset="https://media.chatgptjs.org/images/logos/partners/collage/white.png?3109608">
345
- <img width=888 src="https://media.chatgptjs.org/images/logos/partners/collage/black.png?3109608">
346
- </picture>
354
+ <a href="https://microsoft.com">
355
+ <picture>
356
+ <source type="image/png" media="(prefers-color-scheme: dark)" srcset="https://media.chatgptjs.org/images/logos/partners/microsoft/white.png?963f917">
357
+ <img width=300 src="https://media.chatgptjs.org/images/logos/partners/microsoft/colored.png?963f917">
358
+ </picture>
359
+ </a>
347
360
 
348
361
  </div>
349
362
 
@@ -354,8 +367,8 @@ This library exists thanks to code, translations, issues & ideas from the follow
354
367
  <div align="center">
355
368
 
356
369
  **[Releases](https://github.com/KudoAI/chatgpt.js/releases)** /
357
- [Userguide](https://github.com/KudoAI/chatgpt.js/blob/v3.3.4/docs/USERGUIDE.md) /
370
+ [Userguide](https://github.com/KudoAI/chatgpt.js/blob/v3.4.0/docs/USERGUIDE.md) /
358
371
  [Discuss](https://github.com/KudoAI/chatgpt.js/discussions) /
359
- <a href="#--------------------------------------------------------------------------------english---------简体中文---------繁體中文---------日本---------한국인---------हिंदी---------नेपाली---------deutsch---------español---------français---------italiano---------nederlands---------português---------việt----">Back to top ↑</a>
372
+ [Back to top ↑](#top)
360
373
 
361
374
  </div>
package/chatgpt.js CHANGED
@@ -8,7 +8,7 @@ localStorage.alertQueue = JSON.stringify([]);
8
8
  localStorage.notifyProps = JSON.stringify({ queue: { topRight: [], bottomRight: [], bottomLeft: [], topLeft: [] }});
9
9
 
10
10
  // Define chatgpt API
11
- const chatgpt = { // eslint-disable-line no-redeclare
11
+ const chatgpt = {
12
12
  openAIaccessToken: {}, endpoints: {
13
13
  assets: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js',
14
14
  openAI: {
@@ -64,9 +64,63 @@ const chatgpt = { // eslint-disable-line no-redeclare
64
64
  // [ title/msg = strings, btns = [named functions], checkbox = named function, width (px) = int ] = optional
65
65
  // * Spaces are inserted into button labels by parsing function names in camel/kebab/snake case
66
66
 
67
+ // Init env context
67
68
  const scheme = chatgpt.isDarkMode() ? 'dark' : 'light',
68
69
  isMobile = chatgpt.browser.isMobile();
69
70
 
71
+ // Define event handlers
72
+ const handlers = {
73
+
74
+ dismiss: {
75
+ click(event) {
76
+ if (event.target == event.currentTarget || event.target.closest('[class*="-close-btn]'))
77
+ dismissAlert()
78
+ },
79
+
80
+ key(event) {
81
+ if (!/^(?: |Space|Enter|Return|Esc)/.test(event.key) || ![32, 13, 27].includes(event.keyCode))
82
+ return
83
+ for (const alertId of alertQueue) { // look to handle only if triggering alert is active
84
+ const alert = document.getElementById(alertId)
85
+ if (!alert || alert.style.display == 'none') return
86
+ if (event.key.startsWith('Esc') || event.keyCode == 27) dismissAlert() // and do nothing
87
+ else { // Space/Enter pressed
88
+ const mainButton = alert.querySelector('.modal-buttons').lastChild // look for main button
89
+ if (mainButton) { mainButton.click() ; event.preventDefault() } // click if found
90
+ }
91
+ }
92
+ }
93
+ },
94
+
95
+ drag: {
96
+ mousedown(event) { // find modal, attach listeners, init XY offsets
97
+ if (event.button != 0) return // prevent non-left-click drag
98
+ if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
99
+ chatgpt.draggableElem = event.currentTarget
100
+ chatgpt.draggableElem.style.cursor = 'grabbing'
101
+ event.preventDefault(); // prevent sub-elems like icons being draggable
102
+ ['mousemove', 'mouseup'].forEach(event => document.addEventListener(event, handlers.drag[event]))
103
+ const draggableElemRect = chatgpt.draggableElem.getBoundingClientRect()
104
+ handlers.drag.offsetX = event.clientX - draggableElemRect.left +21
105
+ handlers.drag.offsetY = event.clientY - draggableElemRect.top +12
106
+ },
107
+
108
+ mousemove(event) { // drag modal
109
+ if (!chatgpt.draggableElem) return
110
+ const newX = event.clientX - handlers.drag.offsetX,
111
+ newY = event.clientY - handlers.drag.offsetY
112
+ Object.assign(chatgpt.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
113
+ },
114
+
115
+ mouseup() { // remove listeners, reset chatgpt.draggableElem
116
+ chatgpt.draggableElem.style.cursor = 'inherit';
117
+ ['mousemove', 'mouseup'].forEach(event =>
118
+ document.removeEventListener(event, handlers.drag[event]))
119
+ chatgpt.draggableElem = null
120
+ }
121
+ }
122
+ }
123
+
70
124
  // Create modal parent/children elements
71
125
  const modalContainer = document.createElement('div');
72
126
  modalContainer.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now();
@@ -76,7 +130,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
76
130
  modalMessage = document.createElement('p');
77
131
 
78
132
  // Create/append/update modal style (if missing or outdated)
79
- const thisUpdated = 20231203; // datestamp of last edit for this file's `modalStyle`
133
+ const thisUpdated = 1734685032942; // timestamp of last edit for this file's `modalStyle`
80
134
  let modalStyle = document.querySelector('#chatgpt-modal-style'); // try to select existing style
81
135
  if (!modalStyle || parseInt(modalStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
82
136
  if (!modalStyle) { // outright missing, create/id/attr/append it first
@@ -88,26 +142,31 @@ const chatgpt = { // eslint-disable-line no-redeclare
88
142
  '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
89
143
 
90
144
  // Background styles
91
- + '.chatgpt-modal {'
145
+ + '.chatgpt-modal {'
146
+ + 'pointer-events: auto ;' // override any disabling from site modals (like guest login spam)
92
147
  + 'position: fixed ; top: 0 ; left: 0 ; width: 100% ; height: 100% ;' // expand to full view-port
93
- + 'background-color: rgba(67, 70, 72, 0) ;' // init dim bg but no opacity
94
- + 'transition: background-color 0.05s ease ;' // speed to transition in show alert routine
148
+ + 'transition: background-color 0.25s ease !important ;' // speed to show bg dim
95
149
  + 'display: flex ; justify-content: center ; align-items: center ; z-index: 9999 }' // align
96
150
 
97
151
  // Alert styles
98
152
  + '.chatgpt-modal > div {'
99
- + 'opacity: 0 ; transform: translateX(-2px) translateY(5px) ; max-width: 75vw ; word-wrap: break-word ;'
100
- + 'transition: opacity 0.1s cubic-bezier(.165,.84,.44,1), transform 0.2s cubic-bezier(.165,.84,.44,1) ;'
101
- + `background-color: ${ scheme == 'dark' ? 'black' : 'white' } ;`
102
- + ( scheme != 'dark' ? 'border: 1px solid rgba(0, 0, 0, 0.3) ;' : '' )
153
+ + 'position: absolute ;' // to be click-draggable
154
+ + 'opacity: 0 ;' // to fade-in
155
+ + `border: 1px solid ${ scheme == 'dark' ? 'white' : '#b5b5b5' };`
156
+ + `color: ${ scheme == 'dark' ? 'white' : 'black' };`
157
+ + `background-color: ${ scheme == 'dark' ? 'black' : 'white' };`
158
+ + 'transform: translateX(-3px) translateY(7px) ;' // offset to move-in from
159
+ + 'transition: opacity 0.65s cubic-bezier(.165,.84,.44,1),' // for fade-ins
160
+ + 'transform 0.55s cubic-bezier(.165,.84,.44,1) ;' // for move-ins
161
+ + 'max-width: 75vw ; word-wrap: break-word ;'
103
162
  + 'padding: 20px ; margin: 12px 23px ; border-radius: 15px ; box-shadow: 0 30px 60px rgba(0, 0, 0, .12) ;'
104
163
  + ' -webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ; }'
105
164
  + '.chatgpt-modal h2 { margin-bottom: 9px }'
106
165
  + `.chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}`
107
- + '.chatgpt-modal.animated > div { opacity: 1 ; transform: translateX(0) translateY(0) }'
108
- + '@keyframes alert-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }'
109
- + '50% { opacity: 0.25 ; transform: scale(1.05) }'
110
- + '100% { opacity: 0 ; transform: scale(1.35) }}'
166
+ + '.chatgpt-modal.animated > div { z-index: 13456 ; opacity: 0.98 ; transform: translateX(0) translateY(0) }'
167
+ + '@keyframes alert-zoom-fade-out {'
168
+ + '0% { opacity: 1 } 50% { opacity: 0.25 ; transform: scale(1.05) }'
169
+ + '100% { opacity: 0 ; transform: scale(1.35) }}'
111
170
 
112
171
  // Button styles
113
172
  + '.modal-buttons { display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;'
@@ -140,7 +199,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
140
199
  + `border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' } ;`
141
200
  + 'background-color: black ; position: inherit }'
142
201
  + '.chatgpt-modal input[type="checkbox"]:focus { outline: none ; box-shadow: none }'
143
- );
202
+ );
144
203
  }
145
204
 
146
205
  // Insert text into elements
@@ -210,7 +269,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
210
269
  const modalElems = [closeBtn, modalTitle, modalMessage, modalButtons, checkboxDiv];
211
270
  modalElems.forEach((elem) => { modal.append(elem); });
212
271
  modal.style.width = `${ width || 458 }px`;
213
- modalContainer.append(modal); document.body.append(modalContainer);
272
+ modalContainer.append(modal); document.body.append(modalContainer);
214
273
 
215
274
  // Enqueue alert
216
275
  let alertQueue = JSON.parse(localStorage.alertQueue);
@@ -221,39 +280,22 @@ const chatgpt = { // eslint-disable-line no-redeclare
221
280
  modalContainer.style.display = 'none';
222
281
  if (alertQueue.length === 1) {
223
282
  modalContainer.style.display = '';
224
- setTimeout(() => { // delay non-0 opacity's for transition fx
225
- modalContainer.style.backgroundColor = (
226
- `rgba(67, 70, 72, ${ scheme === 'dark' ? 0.62 : 0.1 })`);
227
- modalContainer.classList.add('animated'); }, 100);
283
+ setTimeout(() => { // dim bg
284
+ modal.parentNode.style.backgroundColor = `rgba(67, 70, 72, ${ scheme == 'dark' ? 0.62 : 0.33 })`
285
+ modal.parentNode.classList.add('animated')
286
+ }, 100) // delay for transition fx
228
287
  }
229
288
 
230
- // Define click/key handlers
231
- const clickHandler = event => { // explicitly defined to support removal post-dismissal
232
- if (event.target == event.currentTarget || event.target instanceof SVGPathElement) dismissAlert(); };
233
- const keyHandler = event => { // to dismiss active alert
234
- const dismissKeys = [' ', 'Spacebar', 'Enter', 'Return', 'Escape', 'Esc'],
235
- dismissKeyCodes = [32, 13, 27];
236
- if (dismissKeys.includes(event.key) || dismissKeyCodes.includes(event.keyCode)) {
237
- for (const alertId of alertQueue) { // look to handle only if triggering alert is active
238
- const alert = document.getElementById(alertId);
239
- if (alert && alert.style.display !== 'none') { // active alert found
240
- if (event.key.includes('Esc') || event.keyCode == 27) // esc pressed
241
- dismissAlert(); // dismiss alert & do nothing
242
- else if ([' ', 'Spacebar', 'Enter', 'Return'].includes(event.key) || [32, 13].includes(event.keyCode)) { // space/enter pressed
243
- const mainButton = alert.querySelector('.modal-buttons').lastChild; // look for main button
244
- if (mainButton) { mainButton.click(); event.preventDefault(); } // click if found
245
- } return;
246
- }}}};
247
-
248
- // Add listeners to dismiss alert
289
+ // Add listeners
249
290
  const dismissElems = [modalContainer, closeBtn, closeSVG, dismissBtn];
250
- dismissElems.forEach(elem => elem.onclick = clickHandler);
251
- document.addEventListener('keydown', keyHandler);
291
+ dismissElems.forEach(elem => elem.onclick = handlers.dismiss.click);
292
+ document.addEventListener('keydown', handlers.dismiss.key);
293
+ modal.onmousedown = handlers.drag.mousedown // enable click-dragging
252
294
 
253
295
  // Define alert dismisser
254
296
  const dismissAlert = () => {
255
297
  modalContainer.style.backgroundColor = 'transparent';
256
- modal.style.animation = 'alert-zoom-fade-out 0.075s ease-out';
298
+ modal.style.animation = 'alert-zoom-fade-out 0.135s ease-out';
257
299
  setTimeout(() => { // delay removal for fade-out
258
300
 
259
301
  // Remove alert
@@ -261,7 +303,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
261
303
  alertQueue = JSON.parse(localStorage.alertQueue);
262
304
  alertQueue.shift(); // + memory
263
305
  localStorage.alertQueue = JSON.stringify(alertQueue); // + storage
264
- document.removeEventListener('keydown', keyHandler); // prevent memory leaks
306
+ document.removeEventListener('keydown', handlers.dismiss.key); // prevent memory leaks
265
307
 
266
308
  // Check for pending alerts in queue
267
309
  if (alertQueue.length > 0) {
@@ -272,7 +314,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
272
314
  }, 500);
273
315
  }
274
316
 
275
- }, 50);
317
+ }, 135);
276
318
  };
277
319
 
278
320
  return modalContainer.id; // if assignment used
@@ -557,7 +599,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
557
599
  .replace('Copy code', '');
558
600
  msgs.push(sender + ': ' + msg);
559
601
  });
560
- transcript = msgs.join('\n\n');
602
+ transcript = msgs.join('\n\n');
561
603
 
562
604
  // ...or from getChatData(chatToGet)
563
605
  } else {
@@ -606,7 +648,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
606
648
  } else { // auto-save to file
607
649
 
608
650
  if (format == 'md') { // remove extraneous HTML + fix file extension
609
- const mdMatch = /<.*(?:<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?)<[^/]/.exec(transcript)[1];
651
+ const mdMatch = /<.*<h1(.|\n)*?href=".*?continue[^"]*".*?\/a>.*?<[^/]/.exec(transcript)[1];
610
652
  transcript = mdMatch || transcript; filename = filename.replace('.html', '.md');
611
653
  }
612
654
  const blob = new Blob([transcript],
@@ -621,20 +663,20 @@ const chatgpt = { // eslint-disable-line no-redeclare
621
663
  focusChatbar() { chatgpt.getChatBox()?.focus(); },
622
664
 
623
665
  footer: {
624
- get() { return document.querySelector('main form')?.parentNode.parentNode.nextElementSibling; },
666
+ get() { return document.querySelector('.min-h-4'); },
625
667
 
626
- hide() {
668
+ hide() {
627
669
  const footer = chatgpt.footer.get();
628
670
  if (!footer) return console.error('Footer element not found!');
629
671
  if (footer.style.visibility == 'hidden') return console.info('Footer already hidden!');
630
- footer.style.visibility = 'hidden'; footer.style.height = '3px';
672
+ footer.style.display = 'none';
631
673
  },
632
674
 
633
675
  show() {
634
676
  const footer = chatgpt.footer.get();
635
677
  if (!footer) return console.error('Footer element not found!');
636
678
  if (footer.style.visibility != 'hidden') return console.info('Footer already shown!');
637
- footer.style.visibility = footer.style.height = 'inherit';
679
+ footer.style.display = 'inherit'
638
680
  }
639
681
  },
640
682
 
@@ -883,17 +925,20 @@ const chatgpt = { // eslint-disable-line no-redeclare
883
925
  },
884
926
 
885
927
  getChatInput() { return chatgpt.getChatBox().firstChild.innerText; },
886
- getContinueButton() { return document.querySelector('button:has([d^="M4.47189"])'); },
928
+ getContinueButton() { return document.querySelector('button.btn:has([d^="M4.47189"])'); },
887
929
  getFooterDiv() { return chatgpt.footer.get(); },
888
930
  getHeaderDiv() { return chatgpt.header.get(); },
889
931
  getLastPrompt() { return chatgpt.getChatData('active', 'msg', 'user', 'latest'); },
890
932
  getLastResponse() { return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest'); },
891
- getNewChatButton() { return document.querySelector('button[data-testid*="new-chat-button"]'); },
933
+
934
+ getNewChatButton() {
935
+ return document.querySelector('button[data-testid*="new-chat-button"], button:has([d^="M15.6729"])'); },
936
+
892
937
  getNewChatLink() { return document.querySelector('nav a[href="/"]'); },
893
938
  getRegenerateButton() { return document.querySelector('button:has([d^="M3.06957"])'); },
894
939
 
895
940
  getResponse() {
896
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
941
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
897
942
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
898
943
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
899
944
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -1147,7 +1192,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1147
1192
  }
1148
1193
 
1149
1194
  else if (element == 'dropdown') {
1150
- if (!attrs?.items || // there no are options to add
1195
+ if (!attrs?.items || // there no are options to add
1151
1196
  !Array.isArray(attrs.items) || // it's not an array
1152
1197
  !attrs.items.length) // the array is empty
1153
1198
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // set default dropdown entry
@@ -1168,7 +1213,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1168
1213
  const addElementsToMenu = () => {
1169
1214
  const optionButtons = document.querySelectorAll('a[role="menuitem"]');
1170
1215
  let cssClasses;
1171
-
1216
+
1172
1217
  for (const navLink of optionButtons)
1173
1218
  if (navLink.textContent == 'Settings') {
1174
1219
  cssClasses = navLink.classList;
@@ -1240,7 +1285,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1240
1285
  + (notificationDiv.isRight ? 'Right' : 'Left');
1241
1286
 
1242
1287
  // Create/append/update notification style (if missing or outdated)
1243
- const thisUpdated = 20231110; // datestamp of last edit for this file's `notifStyle`
1288
+ const thisUpdated = 20231110; // datestamp of last edit for this file's `notifStyle`
1244
1289
  let notifStyle = document.querySelector('#chatgpt-notif-style'); // try to select existing style
1245
1290
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1246
1291
  if (!notifStyle) { // outright missing, create/id/attr/append it first
@@ -1255,7 +1300,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1255
1300
  + 'opacity: 0 ; position: fixed ; z-index: 9999 ; font-size: 1.8rem ; color: white ;' // visibility
1256
1301
  + '-webkit-user-select: none ; -moz-user-select: none ; -ms-user-select: none ; user-select: none ;'
1257
1302
  + `transform: translateX(${ !notificationDiv.isRight ? '-' : '' }35px) ;` // init off-screen for transition fx
1258
- + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(shadow|on)\b/gi.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1303
+ + ( shadow ? ( 'box-shadow: -8px 13px 25px 0 ' + ( /\b(?:shadow|on)\b/i.test(shadow) ? 'gray' : shadow )) : '' ) + '}'
1259
1304
  + '.notif-close-btn { cursor: pointer ; float: right ; position: relative ; right: -4px ; margin-left: -3px ;'
1260
1305
  + 'display: grid }' // top-align for non-OpenAI sites
1261
1306
  + '@keyframes notif-zoom-fade-out { 0% { opacity: 1 ; transform: scale(1) }' // transition out keyframes
@@ -1263,7 +1308,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1263
1308
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1264
1309
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1265
1310
  );
1266
- }
1311
+ }
1267
1312
 
1268
1313
  // Enqueue notification
1269
1314
  let notifyProps = JSON.parse(localStorage.notifyProps);
@@ -1296,7 +1341,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1296
1341
  notificationDiv.style.transition = 'transform 0.15s ease, opacity 0.15s ease';
1297
1342
  }, 10);
1298
1343
 
1299
- // Init delay before hiding
1344
+ // Init delay before hiding
1300
1345
  const hideDelay = fadeDuration > notifDuration ? 0 // don't delay if fade exceeds notification duration
1301
1346
  : notifDuration - fadeDuration; // otherwise delay for difference
1302
1347
 
@@ -1305,7 +1350,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1305
1350
  notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1306
1351
  clearTimeout(dismissFuncTID);
1307
1352
  };
1308
- const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1353
+ const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000); // maintain visibility for `hideDelay` secs, then dismiss
1309
1354
  closeSVG.onclick = dismissNotif; // add to close button clicks
1310
1355
 
1311
1356
  // Destroy notification
@@ -1341,8 +1386,9 @@ const chatgpt = { // eslint-disable-line no-redeclare
1341
1386
  const functionNames = [];
1342
1387
  for (const prop in this) {
1343
1388
  if (typeof this[prop] == 'function') {
1344
- const chatgptIsParent = !Object.keys(this).find(obj => Object.keys(this[obj]).includes(this[prop].name)),
1345
- functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1389
+ const chatgptIsParent = !Object.keys(this)
1390
+ .find(obj => Object.keys(this[obj]).includes(this[prop].name))
1391
+ const functionParent = chatgptIsParent ? 'chatgpt' : 'other';
1346
1392
  functionNames.push([functionParent, prop]);
1347
1393
  } else if (typeof this[prop] == 'object') {
1348
1394
  for (const nestedProp in this[prop]) {
@@ -1398,7 +1444,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1398
1444
 
1399
1445
  renderHTML(node) {
1400
1446
  const reTags = /<([a-z\d]+)\b([^>]*)>([\s\S]*?)<\/\1>/g,
1401
- reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g,
1447
+ reAttributes = /(\S+)=['"]?((?:.(?!['"]?\s+\S+=|[>']))+.)['"]?/g, // eslint-disable-line
1402
1448
  nodeContent = node.childNodes;
1403
1449
 
1404
1450
  // Preserve consecutive spaces + line breaks
@@ -1453,7 +1499,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1453
1499
  continue() { try { chatgpt.getContinueBtn().click(); } catch (err) { console.error(err.message); }},
1454
1500
 
1455
1501
  get() {
1456
- // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1502
+ // * Returns response via DOM by index arg if OpenAI chat page is active, otherwise uses API w/ following args:
1457
1503
  // chatToGet = index|title|id of chat to get (defaults to latest if '' unpassed)
1458
1504
  // responseToGet = index of response to get (defaults to latest if '' unpassed)
1459
1505
  // regenResponseToGet = index of regenerated response to get (defaults to latest if '' unpassed)
@@ -1497,8 +1543,8 @@ const chatgpt = { // eslint-disable-line no-redeclare
1497
1543
  : /^(?:10|ten)(?:th)?$/.test(strPos) ? 10 : 1 )
1498
1544
 
1499
1545
  // Transform base number if suffixed
1500
- * ( /(ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1501
- + ( /teen(th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1546
+ * ( /(?:ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
1547
+ + ( /teen(?:th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
1502
1548
 
1503
1549
  );
1504
1550
  response = responseDivs[nthOfResponse - 1].textContent;
@@ -1682,14 +1728,14 @@ const chatgpt = { // eslint-disable-line no-redeclare
1682
1728
  break;
1683
1729
  }
1684
1730
  }
1685
-
1731
+
1686
1732
  // Apply CSS to make the added elements look like they belong to the website
1687
1733
  this.elements.forEach(element => {
1688
1734
  element.setAttribute('class', cssClasses);
1689
1735
  element.style.maxHeight = element.style.minHeight = '44px'; // Fix the height of the element
1690
1736
  element.style.margin = '2px 0';
1691
1737
  });
1692
-
1738
+
1693
1739
  // Create MutationObserver instance
1694
1740
  const navBar = document.querySelector('nav');
1695
1741
  if (!navBar) return console.error('Sidebar element not found!');
@@ -1746,7 +1792,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1746
1792
  }
1747
1793
 
1748
1794
  else if (element == 'dropdown') {
1749
- if (!attrs?.items || // There no are options to add
1795
+ if (!attrs?.items || // There no are options to add
1750
1796
  !Array.isArray(attrs.items) || // It's not an array
1751
1797
  !attrs.items.length) // The array is empty
1752
1798
  attrs.items = [{ text: '🤖 chatgpt.js option', value: 'chatgpt.js option value' }]; // Set default dropdown entry
@@ -1761,7 +1807,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1761
1807
  newElement.add(optionElement);
1762
1808
  });
1763
1809
  }
1764
-
1810
+
1765
1811
 
1766
1812
  // Fix for blank background on dropdown elements
1767
1813
  if (element == 'dropdown') newElement.style.backgroundColor = 'var(--gray-900, rgb(32, 33, 35))';
@@ -1862,7 +1908,7 @@ const chatgpt = { // eslint-disable-line no-redeclare
1862
1908
  if (!outputLang) return console.error('outputLang (2nd) argument not supplied. Pass a language!');
1863
1909
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] !== 'string')
1864
1910
  return console.error(`Argument ${ i + 1 } must be a string!`);
1865
- chatgpt.send('Translate the following text to ' + outputLang
1911
+ chatgpt.send('Translate the following text to ' + outputLang
1866
1912
  + '. Only reply with the translation.\n\n' + text);
1867
1913
  console.info('Translating text...');
1868
1914
  await chatgpt.isIdle();
@@ -1872,14 +1918,19 @@ const chatgpt = { // eslint-disable-line no-redeclare
1872
1918
  unminify() { chatgpt.code.unminify(); },
1873
1919
 
1874
1920
  uuidv4() {
1875
- let d = new Date().getTime(); // get current timestamp in ms (to ensure UUID uniqueness)
1876
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1877
- const r = ( // generate random nibble
1878
- ( d + (window.crypto.getRandomValues(new Uint32Array(1))[0] / (Math.pow(2, 32) - 1))*16)%16 | 0 );
1879
- d = Math.floor(d/16); // correspond each UUID digit to unique 4-bit chunks of timestamp
1880
- return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16); // generate random hexadecimal digit
1881
- });
1882
- return uuid;
1921
+ try {
1922
+ // use native secure uuid generator when available
1923
+ return crypto.randomUUID();
1924
+ } catch(_e) {
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
+ }
1883
1934
  },
1884
1935
 
1885
1936
  writeCode() { chatgpt.code.write(); }
@@ -2011,7 +2062,7 @@ for (const prop in chatgpt) {
2011
2062
  // Prefix console logs w/ '🤖 chatgpt.js >> '
2012
2063
  const consolePrefix = '🤖 chatgpt.js >> ', ogError = console.error, ogInfo = console.info;
2013
2064
  console.error = (...args) => {
2014
- if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2065
+ if (!args[0].startsWith(consolePrefix)) ogError(consolePrefix + args[0], ...args.slice(1));
2015
2066
  else ogError(...args);
2016
2067
  };
2017
2068
  console.info = (msg) => {