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

Merge branch 'main' into patch-2

parents ccfd8dd7 d2770a2d
......@@ -9,5 +9,5 @@
# ChatGPT
# -----------------------------------------------------------------------------
# see the readme for how to find this
SESSION_TOKEN=
OPENAI_EMAIL=
OPENAI_PASSWORD=
name: Bug Report
description: Create a bug report
labels: ['template: bug']
body:
- type: markdown
attributes:
value: 'If you follow all of the instructions carefully, and your account / IP hasn't been permanently flagged by Cloudflare / OpenAI, then you shouldn't ever get a 403 at this point.'
- type: markdown
attributes:
value: 'My Twitter bot has been running for the past 2 days without a single 403, and others have been able to get it working on Discord. Although it can take a bit of effort to get working, once you have it working, you're set.'
- type: checkboxes
attributes:
label: Verify latest release
description: '`chatgpt@latest` is the latest version of `chatgpt`. Some issues may already be fixed in the latest version, so please verify that your issue reproduces before opening a new issue.'
options:
- label: I verified that the issue exists in the latest `chatgpt` release
required: true
- type: checkboxes
attributes:
label: Verify webapp is working
description: 'Verify that the [ChatGPT webapp](https://chat.openai.com/) is working for you locally using the same browser and account.'
options:
- label: I verify that the ChatGPT webapp is working properly for this account.
required: true
- type: checkboxes
attributes:
label: Verify [restrictions](https://github.com/transitive-bullshit/chatgpt-api#restrictions)
description: 'Verify that you\'ve double-checked all of the restrictions listed in the readme.'
options:
- label: I verify that I've double-checked all of the restrictions.
required: true
- type: textarea
attributes:
label: Provide environment information
description: Please enter `node --version`, `Chrome` version, OS, OS version.
validations:
required: true
- type: input
attributes:
label: Link to the code that reproduces this issue
description: A link to a GitHub repository you own with a minimal reproduction. Minimal reproductions should be forked from this repo and should include only changes that contribute to the issue.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken. **Is `getOpenAIAuth` returning successfully? If so, please print out its values.** If not, please describe what happens in the browser when trying to auth.
validations:
required: true
- type: textarea
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: markdown
attributes:
value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
- type: markdown
attributes:
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
- type: markdown
attributes:
value: These steps are used to improve the docs to ensure the same issue does not happen again. Thanks in advance!
blank_issues_enabled: true
contact_links:
- name: Ask a question
url: https://github.com/vercel/next.js/discussions
about: Ask questions and discuss with other community members
import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora'
import { ChatGPTAPI } from '.'
import { ChatGPTAPI, getOpenAIAuth } from '../src'
dotenv.config()
......@@ -13,7 +13,15 @@ dotenv.config()
* ```
*/
async function main() {
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
const email = process.env.OPENAI_EMAIL
const password = process.env.OPENAI_PASSWORD
const authInfo = await getOpenAIAuth({
email,
password
})
const api = new ChatGPTAPI({ ...authInfo })
await api.ensureAuth()
const conversation = api.getConversation()
......
import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora'
import { ChatGPTAPI } from '.'
import { ChatGPTAPI, getOpenAIAuth } from '../src'
dotenv.config()
......@@ -13,7 +13,15 @@ dotenv.config()
* ```
*/
async function main() {
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
const email = process.env.OPENAI_EMAIL
const password = process.env.OPENAI_PASSWORD
const authInfo = await getOpenAIAuth({
email,
password
})
const api = new ChatGPTAPI({ ...authInfo })
await api.ensureAuth()
const prompt =
......
......@@ -8,6 +8,13 @@
- [constructor](ChatGPTAPI.md#constructor)
### Accessors
- [clearanceToken](ChatGPTAPI.md#clearancetoken)
- [sessionToken](ChatGPTAPI.md#sessiontoken)
- [user](ChatGPTAPI.md#user)
- [userAgent](ChatGPTAPI.md#useragent)
### Methods
- [ensureAuth](ChatGPTAPI.md#ensureauth)
......@@ -24,21 +31,92 @@
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.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` | = **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:45](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/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/c257286/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/c257286/src/chatgpt-api.ts#L131)
___
### user
`get` **user**(): [`User`](../modules.md#user)
Gets the currently signed-in user, if authenticated, `null` otherwise.
#### Returns
[`User`](../modules.md#user)
#### Defined in
[src/chatgpt-api.ts:126](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L126)
___
### userAgent
`get` **userAgent**(): `string`
Gets the current user agent.
#### Returns
`string`
#### Defined in
[chatgpt-api.ts:35](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L35)
[src/chatgpt-api.ts:141](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L141)
## Methods
......@@ -55,7 +133,7 @@ is still valid.
#### Defined in
[chatgpt-api.ts:221](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L221)
[src/chatgpt-api.ts:319](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L319)
___
......@@ -82,7 +160,7 @@ The new conversation instance
#### Defined in
[chatgpt-api.ts:285](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L285)
[src/chatgpt-api.ts:425](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L425)
___
......@@ -99,7 +177,7 @@ the token fails.
#### Defined in
[chatgpt-api.ts:208](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L208)
[src/chatgpt-api.ts:306](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L306)
___
......@@ -125,7 +203,7 @@ A valid access token
#### Defined in
[chatgpt-api.ts:235](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L235)
[src/chatgpt-api.ts:333](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L333)
___
......@@ -156,4 +234,4 @@ The response from ChatGPT
#### Defined in
[chatgpt-api.ts:94](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-api.ts#L94)
[src/chatgpt-api.ts:166](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L166)
......@@ -41,7 +41,7 @@ Creates a new conversation wrapper around the ChatGPT API.
#### Defined in
[chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-conversation.ts#L21)
[src/chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L21)
## Properties
......@@ -51,7 +51,7 @@ Creates a new conversation wrapper around the ChatGPT API.
#### Defined in
[chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-conversation.ts#L10)
[src/chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L10)
___
......@@ -61,7 +61,7 @@ ___
#### Defined in
[chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-conversation.ts#L11)
[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L11)
___
......@@ -71,7 +71,7 @@ ___
#### Defined in
[chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-conversation.ts#L12)
[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L12)
## Methods
......@@ -104,4 +104,4 @@ The response from ChatGPT
#### Defined in
[chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/20c376e/src/chatgpt-conversation.ts#L48)
[src/chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L48)
[chatgpt](../readme.md) / [Exports](../modules.md) / ChatGPTError
# Class: ChatGPTError
## Hierarchy
- `Error`
**`ChatGPTError`**
## Table of contents
### Constructors
- [constructor](ChatGPTError.md#constructor)
### Properties
- [originalError](ChatGPTError.md#originalerror)
- [response](ChatGPTError.md#response)
- [statusCode](ChatGPTError.md#statuscode)
- [statusText](ChatGPTError.md#statustext)
## Constructors
### constructor
**new ChatGPTError**(`message?`)
#### Parameters
| Name | Type |
| :------ | :------ |
| `message?` | `string` |
#### Inherited from
Error.constructor
#### Defined in
node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib/lib.es5.d.ts:1059
**new ChatGPTError**(`message?`, `options?`)
#### Parameters
| Name | Type |
| :------ | :------ |
| `message?` | `string` |
| `options?` | `ErrorOptions` |
#### Inherited from
Error.constructor
#### Defined in
node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts:30
## Properties
### originalError
`Optional` **originalError**: `Error`
#### Defined in
[src/types.ts:298](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L298)
___
### response
`Optional` **response**: `Response`
#### Defined in
[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L297)
___
### statusCode
`Optional` **statusCode**: `number`
#### Defined in
[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L295)
___
### statusText
`Optional` **statusText**: `string`
#### Defined in
[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L296)
This diff is collapsed.
This diff is collapsed.
{
"name": "chatgpt",
"version": "2.0.5",
"version": "2.3.2",
"description": "Node.js client for the unofficial ChatGPT API.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"repository": "transitive-bullshit/chatgpt-api",
......@@ -10,7 +10,6 @@
"types": "./build/index.d.ts",
"exports": {
".": {
"browser": "./build/browser/index.js",
"import": "./build/index.js",
"types": "./build/index.d.ts",
"default": "./build/index.js"
......@@ -20,21 +19,19 @@
"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",
"prepare": "husky install",
"pre-commit": "lint-staged",
"test": "run-p test:*",
"test:unit": "ava",
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
},
"dependencies": {
......@@ -43,7 +40,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",
......@@ -57,14 +57,15 @@
"npm-run-all": "^4.1.5",
"ora": "^6.1.2",
"prettier": "^2.8.0",
"puppeteer": "^19.4.0",
"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.
import test from 'ava'
import dotenv from 'dotenv-safe'
import * as types from './types'
import { ChatGPTAPI } from './chatgpt-api'
dotenv.config()
......@@ -10,16 +11,20 @@ const isCI = !!process.env.CI
test('ChatGPTAPI invalid session token', async (t) => {
t.timeout(30 * 1000) // 30 seconds
t.throws(() => new ChatGPTAPI({ sessionToken: null }), {
t.throws(() => new ChatGPTAPI({ sessionToken: null, clearanceToken: null }), {
message: 'ChatGPT invalid session token'
})
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({ sessionToken: 'invalid' })
const chatgpt = new ChatGPTAPI({
sessionToken: 'invalid',
clearanceToken: 'invalid'
})
await chatgpt.ensureAuth()
},
{
instanceOf: types.ChatGPTError,
message: 'ChatGPT failed to refresh auth token. Error: Unauthorized'
}
)
......@@ -31,13 +36,18 @@ test('ChatGPTAPI valid session token', async (t) => {
}
t.notThrows(
() => new ChatGPTAPI({ sessionToken: 'fake valid session token' })
() =>
new ChatGPTAPI({
sessionToken: 'fake valid session token',
clearanceToken: 'invalid'
})
)
await t.notThrowsAsync(
(async () => {
const chatgpt = new ChatGPTAPI({
sessionToken: process.env.SESSION_TOKEN
sessionToken: process.env.SESSION_TOKEN,
clearanceToken: process.env.CLEARANCE_TOKEN
})
// Don't make any real API calls using our session token if we're running on CI
......@@ -60,10 +70,14 @@ if (!isCI) {
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({ sessionToken: expiredSessionToken })
const chatgpt = new ChatGPTAPI({
sessionToken: expiredSessionToken,
clearanceToken: 'invalid'
})
await chatgpt.ensureAuth()
},
{
instanceOf: types.ChatGPTError,
message:
'ChatGPT failed to refresh auth token. Error: session token may have expired'
}
......@@ -78,7 +92,8 @@ if (!isCI) {
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({
sessionToken: process.env.SESSION_TOKEN
sessionToken: process.env.SESSION_TOKEN,
clearanceToken: process.env.CLEARANCE_TOKEN
})
await chatgpt.sendMessage('test', {
......@@ -97,7 +112,8 @@ if (!isCI) {
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({
sessionToken: process.env.SESSION_TOKEN
sessionToken: process.env.SESSION_TOKEN,
clearanceToken: process.env.CLEARANCE_TOKEN
})
const abortController = new AbortController()
......
This diff is collapsed.
import { createParser } from 'eventsource-parser'
import * as types from './types'
import { fetch } from './fetch'
import { streamAsyncIterable } from './stream-async-iterable'
......@@ -10,7 +11,12 @@ export async function fetchSSE(
const { onMessage, ...fetchOptions } = options
const res = await fetch(url, fetchOptions)
if (!res.ok) {
throw new Error(`ChatGPTAPI error ${res.status || res.statusText}`)
const msg = `ChatGPTAPI error ${res.status || res.statusText}`
const error = new types.ChatGPTError(msg)
error.statusCode = res.status
error.statusText = res.statusText
error.response = res
throw error
}
const parser = createParser((event) => {
......@@ -25,7 +31,7 @@ export async function fetchSSE(
const body: NodeJS.ReadableStream = res.body as any
if (!body.on || !body.read) {
throw new Error('unsupported "fetch" implementation')
throw new types.ChatGPTError('unsupported "fetch" implementation')
}
body.on('readable', () => {
......
/// <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'
import * as fs from 'node:fs'
import * as os from 'node:os'
import delay from 'delay'
import {
type Browser,
type ElementHandle,
type Page,
type Protocol,
type PuppeteerLaunchOptions
} from 'puppeteer'
import puppeteer from 'puppeteer-extra'
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
import * as types from './types'
puppeteer.use(StealthPlugin())
/**
* 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
cookies?: Record<string, Protocol.Network.Cookie>
}
/**
* 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 getOpenAIAuth({
email,
password,
browser,
timeoutMs = 2 * 60 * 1000,
isGoogleLogin = false
}: {
email?: string
password?: string
browser?: Browser
timeoutMs?: number
isGoogleLogin?: boolean
}): Promise<OpenAIAuth> {
let page: Page
let origBrowser = browser
try {
if (!browser) {
browser = await getBrowser()
}
const userAgent = await browser.userAgent()
page = (await browser.pages())[0] || (await browser.newPage())
page.setDefaultTimeout(timeoutMs)
await page.goto('https://chat.openai.com/auth/login')
await checkForChatGPTAtCapacity(page)
// 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'),
page.waitForNavigation({
waitUntil: 'networkidle0'
})
])
let submitP: Promise<void>
if (isGoogleLogin) {
await page.click('button[data-provider="google"]')
await page.waitForSelector('input[type="email"]')
await page.type('input[type="email"]', email, { delay: 10 })
await Promise.all([
page.waitForNavigation(),
await page.keyboard.press('Enter')
])
await page.waitForSelector('input[type="password"]', { visible: true })
await page.type('input[type="password"]', password, { delay: 10 })
submitP = page.keyboard.press('Enter')
} else {
await page.waitForSelector('#username')
await page.type('#username', email, { delay: 10 })
await page.click('button[type="submit"]')
await page.waitForSelector('#password')
await page.type('#password', password, { delay: 10 })
submitP = page.click('button[type="submit"]')
}
await Promise.all([
submitP,
new Promise<void>((resolve, reject) => {
let resolved = false
async function waitForCapacityText() {
if (resolved) {
return
}
try {
await checkForChatGPTAtCapacity(page)
if (!resolved) {
setTimeout(waitForCapacityText, 500)
}
} catch (err) {
if (!resolved) {
resolved = true
return reject(err)
}
}
}
page
.waitForNavigation({
waitUntil: 'networkidle0'
})
.then(() => {
if (!resolved) {
resolved = true
resolve()
}
})
.catch((err) => {
if (!resolved) {
resolved = true
reject(err)
}
})
setTimeout(waitForCapacityText, 500)
})
])
}
const pageCookies = await page.cookies()
const cookies = pageCookies.reduce(
(map, cookie) => ({ ...map, [cookie.name]: cookie }),
{}
)
const authInfo: OpenAIAuth = {
userAgent,
clearanceToken: cookies['cf_clearance']?.value,
sessionToken: cookies['__Secure-next-auth.session-token']?.value,
cookies
}
return authInfo
} catch (err) {
console.error(err)
throw err
} finally {
if (origBrowser) {
if (page) {
await page.close()
}
} else if (browser) {
await browser.close()
}
page = null
browser = null
}
}
/**
* 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) {
return puppeteer.launch({
headless: false,
args: ['--no-sandbox', '--exclude-switches', 'enable-automation'],
ignoreHTTPSErrors: true,
executablePath: defaultChromeExecutablePath(),
...launchOptions
})
}
/**
* Gets the default path to chrome's executable for the current platform.
*/
export const defaultChromeExecutablePath = (): string => {
switch (os.platform()) {
case 'win32':
return 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
case 'darwin':
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
default:
/**
* Since two (2) separate chrome releases exists on linux
* we first do a check to ensure we're executing the right one.
*/
const chromeExists = fs.existsSync('/usr/bin/google-chrome')
return chromeExists
? '/usr/bin/google-chrome'
: '/usr/bin/google-chrome-stable'
}
}
async function checkForChatGPTAtCapacity(page: Page) {
let res: ElementHandle<Element> | null
try {
res = await page.$('[role="alert"]')
console.log('capacity text', res)
} catch (err) {
// ignore errors likely due to navigation
console.warn(err.toString())
}
if (res) {
const error = new types.ChatGPTError('ChatGPT is at capacity')
error.statusCode = 503
throw error
}
}
......@@ -41,7 +41,7 @@ export type User = {
/**
* Email of the user
*/
email: string
email?: string
/**
* Image of the user
......@@ -56,12 +56,12 @@ export type User = {
/**
* Groups the user is in
*/
groups: string[] | []
groups: string[]
/**
* Features the user is in
*/
features: string[] | []
features: string[]
}
/**
......@@ -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
......@@ -287,3 +290,10 @@ export type SendConversationMessageOptions = Omit<
SendMessageOptions,
'conversationId' | 'parentMessageId'
>
export class ChatGPTError extends Error {
statusCode?: number
statusText?: string
response?: Response
originalError?: Error
}
......@@ -11,20 +11,6 @@ export default defineConfig([
sourcemap: true,
minify: false,
shims: true,
dts: true,
external: ['undici']
},
{
entry: ['src/index.ts'],
outDir: 'build/browser',
target: 'chrome89',
platform: 'browser',
format: ['esm'],
splitting: false,
sourcemap: true,
minify: false,
shims: true,
dts: true,
external: ['undici']
dts: true
}
])
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