Commit e5c0b05b authored by Travis Fischer's avatar Travis Fischer Committed by GitHub

Merge pull request #108 from transitive-bullshit/feature/robustness-automation-improvements

parents 2e654be5 6cf9396c
import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora'
import { ChatGPTAPI } from '../src'
import { getOpenAIAuthInfo } from './openai-auth-puppeteer'
import { ChatGPTAPI, getOpenAIAuth } from '../src'
dotenv.config()
......@@ -17,7 +16,7 @@ async function main() {
const email = process.env.EMAIL
const password = process.env.PASSWORD
const authInfo = await getOpenAIAuthInfo({
const authInfo = await getOpenAIAuth({
email,
password
})
......
import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora'
import { ChatGPTAPI } from '../src'
import { getOpenAIAuthInfo } from './openai-auth-puppeteer'
import { ChatGPTAPI, getOpenAIAuth } from '../src'
dotenv.config()
......@@ -17,7 +16,7 @@ async function main() {
const email = process.env.EMAIL
const password = process.env.PASSWORD
const authInfo = await getOpenAIAuthInfo({
const authInfo = await getOpenAIAuth({
email,
password
})
......
......@@ -10,7 +10,10 @@
### Accessors
- [clearanceToken](ChatGPTAPI.md#clearancetoken)
- [sessionToken](ChatGPTAPI.md#sessiontoken)
- [user](ChatGPTAPI.md#user)
- [userAgent](ChatGPTAPI.md#useragent)
### Methods
......@@ -28,26 +31,63 @@
Creates a new client wrapper around the unofficial ChatGPT REST API.
Note that your IP address and `userAgent` must match the same values that you used
to obtain your `clearanceToken`.
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `opts` | `Object` | - |
| `opts.accessToken?` | `string` | - |
| `opts.accessTokenTTL?` | `number` | **`Default Value`** 60000 (60 seconds) |
| `opts.accessToken?` | `string` | **`Default Value`** `undefined` * |
| `opts.accessTokenTTL?` | `number` | **`Default Value`** 1 hour * |
| `opts.apiBaseUrl?` | `string` | **`Default Value`** `'https://chat.openai.com/api'` * |
| `opts.backendApiBaseUrl?` | `string` | **`Default Value`** `'https://chat.openai.com/backend-api'` * |
| `opts.clearanceToken` | `string` | - |
| `opts.clearanceToken` | `string` | = **Required** Cloudflare `cf_clearance` cookie value (see readme for instructions) |
| `opts.debug?` | `boolean` | **`Default Value`** `false` * |
| `opts.headers?` | `Record`<`string`, `string`\> | **`Default Value`** `undefined` * |
| `opts.markdown?` | `boolean` | **`Default Value`** `true` * |
| `opts.sessionToken` | `string` | = **Required** OpenAI session token which can be found in a valid session's cookies (see readme for instructions) |
| `opts.userAgent?` | `string` | **`Default Value`** `'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'` * |
| `opts.userAgent?` | `string` | **`Default Value`** `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'` * |
#### Defined in
[src/chatgpt-api.ts:39](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L39)
[src/chatgpt-api.ts:45](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L45)
## Accessors
### clearanceToken
`get` **clearanceToken**(): `string`
Gets the current Cloudflare clearance token (`cf_clearance` cookie value).
#### Returns
`string`
#### Defined in
[src/chatgpt-api.ts:136](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L136)
___
### sessionToken
`get` **sessionToken**(): `string`
Gets the current session token.
#### Returns
`string`
#### Defined in
[src/chatgpt-api.ts:131](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L131)
___
### user
`get` **user**(): [`User`](../modules.md#user)
......@@ -60,7 +100,23 @@ Gets the currently signed-in user, if authenticated, `null` otherwise.
#### Defined in
[src/chatgpt-api.ts:98](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L98)
[src/chatgpt-api.ts:126](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L126)
___
### userAgent
`get` **userAgent**(): `string`
Gets the current user agent.
#### Returns
`string`
#### Defined in
[src/chatgpt-api.ts:141](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L141)
## Methods
......@@ -77,7 +133,7 @@ is still valid.
#### Defined in
[src/chatgpt-api.ts:250](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L250)
[src/chatgpt-api.ts:319](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L319)
___
......@@ -104,7 +160,7 @@ The new conversation instance
#### Defined in
[src/chatgpt-api.ts:344](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L344)
[src/chatgpt-api.ts:425](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L425)
___
......@@ -121,7 +177,7 @@ the token fails.
#### Defined in
[src/chatgpt-api.ts:237](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L237)
[src/chatgpt-api.ts:306](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L306)
___
......@@ -147,7 +203,7 @@ A valid access token
#### Defined in
[src/chatgpt-api.ts:264](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L264)
[src/chatgpt-api.ts:333](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L333)
___
......@@ -178,4 +234,4 @@ The response from ChatGPT
#### Defined in
[src/chatgpt-api.ts:121](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-api.ts#L121)
[src/chatgpt-api.ts:166](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-api.ts#L166)
......@@ -41,7 +41,7 @@ Creates a new conversation wrapper around the ChatGPT API.
#### Defined in
[src/chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-conversation.ts#L21)
[src/chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-conversation.ts#L21)
## Properties
......@@ -51,7 +51,7 @@ Creates a new conversation wrapper around the ChatGPT API.
#### Defined in
[src/chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-conversation.ts#L10)
[src/chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-conversation.ts#L10)
___
......@@ -61,7 +61,7 @@ ___
#### Defined in
[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-conversation.ts#L11)
[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-conversation.ts#L11)
___
......@@ -71,7 +71,7 @@ ___
#### Defined in
[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-conversation.ts#L12)
[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-conversation.ts#L12)
## Methods
......@@ -104,4 +104,4 @@ The response from ChatGPT
#### Defined in
[src/chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/chatgpt-conversation.ts#L48)
[src/chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/chatgpt-conversation.ts#L48)
......@@ -66,7 +66,7 @@ node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib/lib.es2022.error
#### Defined in
[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L295)
[src/types.ts:298](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L298)
___
......@@ -76,7 +76,7 @@ ___
#### Defined in
[src/types.ts:294](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L294)
[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L297)
___
......@@ -86,7 +86,7 @@ ___
#### Defined in
[src/types.ts:292](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L292)
[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L295)
___
......@@ -96,4 +96,4 @@ ___
#### Defined in
[src/types.ts:293](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L293)
[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L296)
......@@ -17,6 +17,7 @@
- [ConversationJSONBody](modules.md#conversationjsonbody)
- [ConversationResponseEvent](modules.md#conversationresponseevent)
- [Message](modules.md#message)
- [MessageActionType](modules.md#messageactiontype)
- [MessageContent](modules.md#messagecontent)
- [MessageFeedbackJSONBody](modules.md#messagefeedbackjsonbody)
- [MessageFeedbackRating](modules.md#messagefeedbackrating)
......@@ -27,6 +28,7 @@
- [ModelsResult](modules.md#modelsresult)
- [ModerationsJSONBody](modules.md#moderationsjsonbody)
- [ModerationsJSONResult](modules.md#moderationsjsonresult)
- [OpenAIAuth](modules.md#openaiauth)
- [Prompt](modules.md#prompt)
- [PromptContent](modules.md#promptcontent)
- [Role](modules.md#role)
......@@ -37,6 +39,8 @@
### Functions
- [getBrowser](modules.md#getbrowser)
- [getOpenAIAuth](modules.md#getopenaiauth)
- [markdownToText](modules.md#markdowntotext)
## Type Aliases
......@@ -47,7 +51,7 @@
#### Defined in
[src/types.ts:109](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L109)
[src/types.ts:109](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L109)
___
......@@ -57,7 +61,7 @@ ___
#### Defined in
[src/types.ts:1](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L1)
[src/types.ts:1](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L1)
___
......@@ -79,7 +83,7 @@ https://chat.openapi.com/backend-api/conversation
#### Defined in
[src/types.ts:134](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L134)
[src/types.ts:134](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L134)
___
......@@ -97,7 +101,7 @@ ___
#### Defined in
[src/types.ts:251](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L251)
[src/types.ts:251](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L251)
___
......@@ -122,7 +126,17 @@ ___
#### Defined in
[src/types.ts:257](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L257)
[src/types.ts:257](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L257)
___
### MessageActionType
Ƭ **MessageActionType**: ``"next"`` \| ``"variant"``
#### Defined in
[src/types.ts:276](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L276)
___
......@@ -139,7 +153,7 @@ ___
#### Defined in
[src/types.ts:270](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L270)
[src/types.ts:270](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L270)
___
......@@ -161,7 +175,7 @@ https://chat.openapi.com/backend-api/conversation/message_feedback
#### Defined in
[src/types.ts:193](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L193)
[src/types.ts:193](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L193)
___
......@@ -171,7 +185,7 @@ ___
#### Defined in
[src/types.ts:249](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L249)
[src/types.ts:249](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L249)
___
......@@ -191,7 +205,7 @@ ___
#### Defined in
[src/types.ts:222](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L222)
[src/types.ts:222](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L222)
___
......@@ -201,7 +215,7 @@ ___
#### Defined in
[src/types.ts:220](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L220)
[src/types.ts:220](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L220)
___
......@@ -211,7 +225,7 @@ ___
#### Defined in
[src/types.ts:275](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L275)
[src/types.ts:275](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L275)
___
......@@ -229,7 +243,7 @@ ___
#### Defined in
[src/types.ts:77](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L77)
[src/types.ts:77](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L77)
___
......@@ -247,7 +261,7 @@ https://chat.openapi.com/backend-api/models
#### Defined in
[src/types.ts:70](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L70)
[src/types.ts:70](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L70)
___
......@@ -266,7 +280,7 @@ https://chat.openapi.com/backend-api/moderations
#### Defined in
[src/types.ts:97](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L97)
[src/types.ts:97](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L97)
___
......@@ -286,7 +300,29 @@ https://chat.openapi.com/backend-api/moderations
#### Defined in
[src/types.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L114)
[src/types.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L114)
___
### OpenAIAuth
Ƭ **OpenAIAuth**: `Object`
Represents everything that's required to pass into `ChatGPTAPI` in order
to authenticate with the unofficial ChatGPT API.
#### Type declaration
| Name | Type |
| :------ | :------ |
| `clearanceToken` | `string` |
| `cookies?` | `Record`<`string`, `Protocol.Network.Cookie`\> |
| `sessionToken` | `string` |
| `userAgent` | `string` |
#### Defined in
[src/openai-auth.ts:17](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/openai-auth.ts#L17)
___
......@@ -304,7 +340,7 @@ ___
#### Defined in
[src/types.ts:161](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L161)
[src/types.ts:161](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L161)
___
......@@ -321,7 +357,7 @@ ___
#### Defined in
[src/types.ts:178](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L178)
[src/types.ts:178](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L178)
___
......@@ -331,7 +367,7 @@ ___
#### Defined in
[src/types.ts:3](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L3)
[src/types.ts:3](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L3)
___
......@@ -341,7 +377,7 @@ ___
#### Defined in
[src/types.ts:286](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L286)
[src/types.ts:289](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L289)
___
......@@ -354,7 +390,9 @@ ___
| Name | Type |
| :------ | :------ |
| `abortSignal?` | `AbortSignal` |
| `action?` | [`MessageActionType`](modules.md#messageactiontype) |
| `conversationId?` | `string` |
| `messageId?` | `string` |
| `onConversationResponse?` | (`response`: [`ConversationResponseEvent`](modules.md#conversationresponseevent)) => `void` |
| `onProgress?` | (`partialResponse`: `string`) => `void` |
| `parentMessageId?` | `string` |
......@@ -362,7 +400,7 @@ ___
#### Defined in
[src/types.ts:277](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L277)
[src/types.ts:278](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L278)
___
......@@ -383,7 +421,7 @@ https://chat.openapi.com/api/auth/session
#### Defined in
[src/types.ts:8](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L8)
[src/types.ts:8](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L8)
___
......@@ -405,10 +443,72 @@ ___
#### Defined in
[src/types.ts:30](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/types.ts#L30)
[src/types.ts:30](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/types.ts#L30)
## Functions
### getBrowser
**getBrowser**(`launchOptions?`): `Promise`<`Browser`\>
Launches a non-puppeteer instance of Chrome. Note that in my testing, I wasn't
able to use the built-in `puppeteer` version of Chromium because Cloudflare
recognizes it and blocks access.
#### Parameters
| Name | Type |
| :------ | :------ |
| `launchOptions?` | `PuppeteerLaunchOptions` |
#### Returns
`Promise`<`Browser`\>
#### Defined in
[src/openai-auth.ts:127](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/openai-auth.ts#L127)
___
### getOpenAIAuth
**getOpenAIAuth**(`__namedParameters`): `Promise`<[`OpenAIAuth`](modules.md#openaiauth)\>
Bypasses OpenAI's use of Cloudflare to get the cookies required to use
ChatGPT. Uses Puppeteer with a stealth plugin under the hood.
If you pass `email` and `password`, then it will log into the account and
include a `sessionToken` in the response.
If you don't pass `email` and `password`, then it will just return a valid
`clearanceToken`.
This can be useful because `clearanceToken` expires after ~2 hours, whereas
`sessionToken` generally lasts much longer. We recommend renewing your
`clearanceToken` every hour or so and creating a new instance of `ChatGPTAPI`
with your updated credentials.
#### Parameters
| Name | Type |
| :------ | :------ |
| `__namedParameters` | `Object` |
| `__namedParameters.browser?` | `Browser` |
| `__namedParameters.email?` | `string` |
| `__namedParameters.password?` | `string` |
| `__namedParameters.timeoutMs?` | `number` |
#### Returns
`Promise`<[`OpenAIAuth`](modules.md#openaiauth)\>
#### Defined in
[src/openai-auth.ts:39](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/openai-auth.ts#L39)
___
### markdownToText
**markdownToText**(`markdown?`): `string`
......@@ -425,4 +525,4 @@ ___
#### Defined in
[src/utils.ts:4](https://github.com/transitive-bullshit/chatgpt-api/blob/8e1cde4/src/utils.ts#L4)
[src/utils.ts:4](https://github.com/transitive-bullshit/chatgpt-api/blob/a48c177/src/utils.ts#L4)
This diff is collapsed.
......@@ -20,14 +20,13 @@
"build"
],
"engines": {
"node": ">=16.8"
"node": ">=18"
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"clean": "del build",
"prebuild": "run-s clean",
"postbuild": "[ -n CI ] && sed -i '' 's/await import(\"undici\")/null/' build/browser/index.js || echo 'skipping postbuild on CI'",
"predev": "run-s clean",
"pretest": "run-s build",
"docs": "typedoc",
......@@ -42,7 +41,10 @@
"p-timeout": "^6.0.0",
"remark": "^14.0.2",
"strip-markdown": "^5.0.0",
"uuid": "^9.0.0"
"delay": "^5.0.0",
"uuid": "^9.0.0",
"puppeteer-extra": "^3.3.4",
"puppeteer-extra-plugin-stealth": "^2.11.1"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
......@@ -50,7 +52,6 @@
"@types/uuid": "^9.0.0",
"ava": "^5.1.0",
"del-cli": "^5.0.0",
"delay": "^5.0.0",
"dotenv-safe": "^8.2.0",
"husky": "^8.0.2",
"lint-staged": "^13.0.3",
......@@ -58,16 +59,14 @@
"ora": "^6.1.2",
"prettier": "^2.8.0",
"puppeteer": "^19.4.0",
"puppeteer-extra": "^3.3.4",
"puppeteer-extra-plugin-stealth": "^2.11.1",
"tsup": "^6.5.0",
"tsx": "^3.12.1",
"typedoc": "^0.23.21",
"typedoc-plugin-markdown": "^3.13.6",
"typescript": "^4.9.3"
},
"optionalDependencies": {
"undici": "^5.13.0"
"peerDependencies": {
"puppeteer": "*"
},
"lint-staged": {
"*.{ts,tsx}": [
......
This diff is collapsed.
This diff is collapsed.
......@@ -16,27 +16,35 @@ export class ChatGPTAPI {
protected _sessionToken: string
protected _clearanceToken: string
protected _markdown: boolean
protected _debug: boolean
protected _apiBaseUrl: string
protected _backendApiBaseUrl: string
protected _userAgent: string
protected _headers: Record<string, string>
protected _user: types.User | null = null
// Stores access tokens for `accessTokenTTL` milliseconds before needing to refresh
protected _accessTokenCache: ExpiryMap<string, string>
protected _user: types.User | null = null
/**
* Creates a new client wrapper around the unofficial ChatGPT REST API.
*
* Note that your IP address and `userAgent` must match the same values that you used
* to obtain your `clearanceToken`.
*
* @param opts.sessionToken = **Required** OpenAI session token which can be found in a valid session's cookies (see readme for instructions)
* @param opts.clearanceToken = **Required** Cloudflare `cf_clearance` cookie value (see readme for instructions)
* @param apiBaseUrl - Optional override; the base URL for ChatGPT webapp's API (`/api`)
* @param backendApiBaseUrl - Optional override; the base URL for the ChatGPT backend API (`/backend-api`)
* @param userAgent - Optional override; the `user-agent` header to use with ChatGPT requests
* @param accessTokenTTL - Optional override; how long in milliseconds access tokens should last before being forcefully refreshed
* @param accessToken - Optional default access token if you already have a valid one generated
* @param heaaders - Optional additional HTTP headers to be added to each `fetch` request
* @param debug - Optional enables logging debugging into to stdout
*/
constructor(opts: {
sessionToken: string
clearanceToken: string
/** @defaultValue `true` **/
......@@ -48,15 +56,20 @@ export class ChatGPTAPI {
/** @defaultValue `'https://chat.openai.com/backend-api'` **/
backendApiBaseUrl?: string
/** @defaultValue `'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'` **/
/** @defaultValue `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'` **/
userAgent?: string
/** @defaultValue 1 hour */
/** @defaultValue 1 hour **/
accessTokenTTL?: number
/** @defaultValue `undefined` **/
accessToken?: string
/** @defaultValue `undefined` **/
headers?: Record<string, string>
/** @defaultValue `false` **/
debug?: boolean
}) {
const {
sessionToken,
......@@ -67,12 +80,14 @@ export class ChatGPTAPI {
userAgent = USER_AGENT,
accessTokenTTL = 60 * 60000, // 1 hour
accessToken,
headers
headers,
debug = false
} = opts
this._sessionToken = sessionToken
this._clearanceToken = clearanceToken
this._markdown = !!markdown
this._debug = !!debug
this._apiBaseUrl = apiBaseUrl
this._backendApiBaseUrl = backendApiBaseUrl
this._userAgent = userAgent
......@@ -112,6 +127,21 @@ export class ChatGPTAPI {
return this._user
}
/** Gets the current session token. */
get sessionToken() {
return this._sessionToken
}
/** Gets the current Cloudflare clearance token (`cf_clearance` cookie value). */
get clearanceToken() {
return this._clearanceToken
}
/** Gets the current user agent. */
get userAgent() {
return this._userAgent
}
/**
* Sends a message to ChatGPT, waits for the response to resolve, and returns
* the response.
......@@ -124,6 +154,8 @@ export class ChatGPTAPI {
* @param message - The prompt message to send
* @param opts.conversationId - Optional ID of a conversation to continue
* @param opts.parentMessageId - Optional ID of the previous message in the conversation
* @param opts.messageId - Optional ID of the message to send (defaults to a random UUID)
* @param opts.action - Optional ChatGPT `action` (either `next` or `variant`)
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
* @param opts.onConversationResponse - Optional callback which will be invoked every time the partial response is updated with the full conversation response
......@@ -138,6 +170,8 @@ export class ChatGPTAPI {
const {
conversationId,
parentMessageId = uuidv4(),
messageId = uuidv4(),
action = 'next',
timeoutMs,
onProgress,
onConversationResponse
......@@ -154,10 +188,10 @@ export class ChatGPTAPI {
const accessToken = await this.refreshAccessToken()
const body: types.ConversationJSONBody = {
action: 'next',
action,
messages: [
{
id: uuidv4(),
id: messageId,
role: 'user',
content: {
content_type: 'text',
......@@ -173,19 +207,25 @@ export class ChatGPTAPI {
body.conversation_id = conversationId
}
const url = `${this._backendApiBaseUrl}/conversation`
let response = ''
const responseP = new Promise<string>((resolve, reject) => {
fetchSSE(url, {
method: 'POST',
headers: {
const url = `${this._backendApiBaseUrl}/conversation`
const headers = {
...this._headers,
Authorization: `Bearer ${accessToken}`,
Accept: 'text/event-stream',
'Content-Type': 'application/json',
Cookie: `cf_clearance=${this._clearanceToken}`
},
}
if (this._debug) {
console.log('POST', url, { body, headers })
}
fetchSSE(url, {
method: 'POST',
headers,
body: JSON.stringify(body),
signal: abortSignal,
onMessage: (data: string) => {
......@@ -222,7 +262,23 @@ export class ChatGPTAPI {
reject(err)
}
}
}).catch(reject)
}).catch((err) => {
const errMessageL = err.toString().toLowerCase()
if (
response &&
(errMessageL === 'error: typeerror: terminated' ||
errMessageL === 'typeerror: terminated')
) {
// OpenAI sometimes forcefully terminates the socket from their end before
// the HTTP request has resolved cleanly. In my testing, these cases tend to
// happen when OpenAI has already send the last `response`, so we can ignore
// the `fetch` error in this case.
return resolve(response)
} else {
return reject(err)
}
})
})
if (timeoutMs) {
......@@ -282,14 +338,18 @@ export class ChatGPTAPI {
let response: Response
try {
const url = `${this._apiBaseUrl}/auth/session`
const headers = {
...this._headers,
cookie: `cf_clearance=${this._clearanceToken}; __Secure-next-auth.session-token=${this._sessionToken}`,
accept: '*/*'
}
console.log(`${this._apiBaseUrl}/auth/session`, headers)
const res = await fetch(`${this._apiBaseUrl}/auth/session`, {
if (this._debug) {
console.log('GET', url, headers)
}
const res = await fetch(url, {
headers
}).then((r) => {
response = r
......@@ -339,6 +399,10 @@ export class ChatGPTAPI {
this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken)
return accessToken
} catch (err: any) {
if (this._debug) {
console.error(err)
}
const error = new types.ChatGPTError(
`ChatGPT failed to refresh auth token. ${err.toString()}`
)
......
/// <reference lib="dom" />
let _undici: any
// Use `undici` for node.js 16 and 17
// Use `fetch` for node.js >= 18
// Use `fetch` for all other environments, including browsers
// NOTE: The top-level await is removed in a `postbuild` npm script for the
// browser build
const fetch =
globalThis.fetch ??
async function undiciFetchWrapper(
...args: Parameters<typeof globalThis.fetch>
): Promise<Response> {
if (!_undici) {
_undici = await import('undici')
}
const fetch = globalThis.fetch
if (typeof _undici?.fetch !== 'function') {
if (typeof fetch !== 'function') {
throw new Error(
'Invalid undici installation; please make sure undici is installed correctly in your node_modules. Note that this package requires Node.js >= 16.8'
'Invalid environment: global fetch not defined; `chatgpt` requires Node.js >= 18 at the moment due to Cloudflare protections'
)
}
return _undici.fetch(...args)
}
}
export { fetch }
......@@ -2,3 +2,4 @@ export * from './chatgpt-api'
export * from './chatgpt-conversation'
export * from './types'
export * from './utils'
export * from './openai-auth'
......@@ -10,7 +10,11 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth'
puppeteer.use(StealthPlugin())
export type OpenAIAuthInfo = {
/**
* Represents everything that's required to pass into `ChatGPTAPI` in order
* to authenticate with the unofficial ChatGPT API.
*/
export type OpenAIAuth = {
userAgent: string
clearanceToken: string
sessionToken: string
......@@ -20,18 +24,29 @@ export type OpenAIAuthInfo = {
/**
* Bypasses OpenAI's use of Cloudflare to get the cookies required to use
* ChatGPT. Uses Puppeteer with a stealth plugin under the hood.
*
* If you pass `email` and `password`, then it will log into the account and
* include a `sessionToken` in the response.
*
* If you don't pass `email` and `password`, then it will just return a valid
* `clearanceToken`.
*
* This can be useful because `clearanceToken` expires after ~2 hours, whereas
* `sessionToken` generally lasts much longer. We recommend renewing your
* `clearanceToken` every hour or so and creating a new instance of `ChatGPTAPI`
* with your updated credentials.
*/
export async function getOpenAIAuthInfo({
export async function getOpenAIAuth({
email,
password,
timeout = 2 * 60 * 1000,
timeoutMs = 2 * 60 * 1000,
browser
}: {
email: string
password: string
timeout?: number
email?: string
password?: string
timeoutMs?: number
browser?: Browser
}): Promise<OpenAIAuthInfo> {
}): Promise<OpenAIAuth> {
let page: Page
let origBrowser = browser
......@@ -42,12 +57,18 @@ export async function getOpenAIAuthInfo({
const userAgent = await browser.userAgent()
page = (await browser.pages())[0] || (await browser.newPage())
page.setDefaultTimeout(timeout)
page.setDefaultTimeout(timeoutMs)
await page.goto('https://chat.openai.com/auth/login')
await page.waitForSelector('#__next .btn-primary', { timeout })
// NOTE: this is where you may encounter a CAPTCHA
await page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs })
// once we get to this point, the Cloudflare cookies are available
await delay(1000)
// login as well (optional)
if (email && password) {
await Promise.all([
page.click('#__next .btn-primary'),
......@@ -73,7 +94,7 @@ export async function getOpenAIAuthInfo({
{}
)
const authInfo: OpenAIAuthInfo = {
const authInfo: OpenAIAuth = {
userAgent,
clearanceToken: cookies['cf_clearance']?.value,
sessionToken: cookies['__Secure-next-auth.session-token']?.value,
......@@ -83,7 +104,7 @@ export async function getOpenAIAuthInfo({
return authInfo
} catch (err) {
console.error(err)
throw null
throw err
} finally {
if (origBrowser) {
if (page) {
......@@ -98,6 +119,11 @@ export async function getOpenAIAuthInfo({
}
}
/**
* Launches a non-puppeteer instance of Chrome. Note that in my testing, I wasn't
* able to use the built-in `puppeteer` version of Chromium because Cloudflare
* recognizes it and blocks access.
*/
export async function getBrowser(launchOptions?: PuppeteerLaunchOptions) {
const macChromePath =
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
......
......@@ -273,10 +273,13 @@ export type MessageContent = {
}
export type MessageMetadata = any
export type MessageActionType = 'next' | 'variant'
export type SendMessageOptions = {
conversationId?: string
parentMessageId?: string
messageId?: string
action?: MessageActionType
timeoutMs?: number
onProgress?: (partialResponse: string) => void
onConversationResponse?: (response: ConversationResponseEvent) => void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment