From dd872320ae524b6c15a0a44999e6c83b85391301 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 14 Dec 2022 16:14:35 -0600 Subject: [PATCH 01/12] feat: trying to fix capacity testing --- src/openai-auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/openai-auth.ts b/src/openai-auth.ts index e5d060c..121fd42 100644 --- a/src/openai-auth.ts +++ b/src/openai-auth.ts @@ -229,13 +229,14 @@ async function checkForChatGPTAtCapacity(page: Page) { let res: ElementHandle[] try { + // res = await page.$('[role="alert"]') res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]") } catch (err) { // ignore errors likely due to navigation } if (res?.length) { - const error = new types.ChatGPTError(`ChatGPT is at capacity: ${res}`) + const error = new types.ChatGPTError('ChatGPT is at capacity') error.statusCode = 503 throw error } From 2e0990e9bee206e9f697551dc71e7fe55ed7d009 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 14 Dec 2022 22:41:43 -0600 Subject: [PATCH 02/12] feat: add ChatGPTAPIBrowser for increased robustness; less efficient but less 429/403/503 errors --- demos/demo.ts | 13 +- package.json | 2 + pnpm-lock.yaml | 28 +++++ src/chatgpt-api-browser.ts | 236 +++++++++++++++++++++++++++++++++++++ src/chatgpt-api.ts | 40 +++++++ src/index.ts | 1 + src/openai-auth.ts | 103 ++++++++++++---- 7 files changed, 391 insertions(+), 32 deletions(-) create mode 100644 src/chatgpt-api-browser.ts diff --git a/demos/demo.ts b/demos/demo.ts index 7f43241..0c18173 100644 --- a/demos/demo.ts +++ b/demos/demo.ts @@ -1,7 +1,7 @@ import dotenv from 'dotenv-safe' import { oraPromise } from 'ora' -import { ChatGPTAPI, getOpenAIAuth } from '../src' +import { ChatGPTAPIBrowser } from '../src' dotenv.config() @@ -16,13 +16,9 @@ async function main() { 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 api = new ChatGPTAPIBrowser({ email, password }) + const res = await api.init() + console.log('init result', res) const prompt = 'Write a python version of bubble sort. Do not include example usage.' @@ -31,6 +27,7 @@ async function main() { text: prompt }) + await api.close() return response } diff --git a/package.json b/package.json index 9de81cb..5590c18 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,10 @@ "delay": "^5.0.0", "eventsource-parser": "^0.0.5", "expiry-map": "^2.0.0", + "html-to-md": "^0.8.3", "p-timeout": "^6.0.0", "puppeteer-extra": "^3.3.4", + "puppeteer-extra-plugin-recaptcha": "^3.6.6", "puppeteer-extra-plugin-stealth": "^2.11.1", "remark": "^14.0.2", "strip-markdown": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38dbc65..ae8fa64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,7 @@ specifiers: dotenv-safe: ^8.2.0 eventsource-parser: ^0.0.5 expiry-map: ^2.0.0 + html-to-md: ^0.8.3 husky: ^8.0.2 lint-staged: ^13.0.3 npm-run-all: ^4.1.5 @@ -18,6 +19,7 @@ specifiers: prettier: ^2.8.0 puppeteer: ^19.4.0 puppeteer-extra: ^3.3.4 + puppeteer-extra-plugin-recaptcha: ^3.6.6 puppeteer-extra-plugin-stealth: ^2.11.1 remark: ^14.0.2 strip-markdown: ^5.0.0 @@ -32,8 +34,10 @@ dependencies: delay: 5.0.0 eventsource-parser: 0.0.5 expiry-map: 2.0.0 + html-to-md: 0.8.3 p-timeout: 6.0.0 puppeteer-extra: 3.3.4_puppeteer@19.4.0 + puppeteer-extra-plugin-recaptcha: 3.6.6_puppeteer-extra@3.3.4 puppeteer-extra-plugin-stealth: 2.11.1_puppeteer-extra@3.3.4 remark: 14.0.2 strip-markdown: 5.0.0 @@ -1789,6 +1793,10 @@ packages: lru-cache: 6.0.0 dev: true + /html-to-md/0.8.3: + resolution: {integrity: sha512-Va+bB1YOdD6vMRDue9/l7YxbERgwOgsos4erUDRfRN6YE0B2Wbbw8uAj6xZJk9A9vrjVy7mG/WLlhDw6RXfgsA==} + dev: false + /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -3065,6 +3073,26 @@ packages: - supports-color - utf-8-validate + /puppeteer-extra-plugin-recaptcha/3.6.6_puppeteer-extra@3.3.4: + resolution: {integrity: sha512-SVbmL+igGX8m0Qg9dn85trWDghbfUCTG/QUHYscYx5XgMZVVb0/v0a6MqbPdHoKmBx5BS2kLd6rorMlncMcXdw==} + engines: {node: '>=9.11.2'} + peerDependencies: + playwright-extra: '*' + puppeteer-extra: '*' + peerDependenciesMeta: + playwright-extra: + optional: true + puppeteer-extra: + optional: true + dependencies: + debug: 4.3.4 + merge-deep: 3.0.3 + puppeteer-extra: 3.3.4_puppeteer@19.4.0 + puppeteer-extra-plugin: 3.2.2_puppeteer-extra@3.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /puppeteer-extra-plugin-stealth/2.11.1_puppeteer-extra@3.3.4: resolution: {integrity: sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==} engines: {node: '>=8'} diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts new file mode 100644 index 0000000..377bf7a --- /dev/null +++ b/src/chatgpt-api-browser.ts @@ -0,0 +1,236 @@ +import delay from 'delay' +import html2md from 'html-to-md' +import { type Browser, type HTTPResponse, type Page } from 'puppeteer' + +import * as types from './types' +import { getBrowser, getOpenAIAuth } from './openai-auth' + +export class ChatGPTAPIBrowser { + protected _markdown: boolean + protected _debug: boolean + protected _isGoogleLogin: boolean + protected _captchaToken: string + + protected _email: string + protected _password: string + + protected _browser: Browser + protected _page: Page + + /** + * Creates a new client wrapper for automating the ChatGPT webapp. + */ + constructor(opts: { + email: string + password: string + + /** @defaultValue `true` **/ + markdown?: boolean + + /** @defaultValue `false` **/ + debug?: boolean + + isGoogleLogin?: boolean + captchaToken?: string + }) { + const { + email, + password, + markdown = true, + debug = false, + isGoogleLogin = false, + captchaToken + } = opts + + this._email = email + this._password = password + + this._markdown = !!markdown + this._debug = !!debug + this._isGoogleLogin = !!isGoogleLogin + this._captchaToken = captchaToken + } + + async init() { + if (this._browser) { + await this._browser.close() + this._page = null + this._browser = null + } + + this._browser = await getBrowser({ captchaToken: this._captchaToken }) + this._page = + (await this._browser.pages())[0] || (await this._browser.newPage()) + + // bypass cloudflare and login + await getOpenAIAuth({ + email: this._email, + password: this._password, + browser: this._browser, + page: this._page, + isGoogleLogin: this._isGoogleLogin + }) + + const chatUrl = 'https://chat.openai.com/chat' + const url = this._page.url().replace(/\/$/, '') + + if (url !== chatUrl) { + await this._page.goto(chatUrl, { + waitUntil: 'networkidle0' + }) + } + + // dismiss welcome modal + do { + const modalSelector = '[data-headlessui-state="open"]' + + if (!(await this._page.$(modalSelector))) { + break + } + + try { + await this._page.click(`${modalSelector} button:last-child`) + } catch (err) { + // "next" button not found in welcome modal + break + } + + await delay(500) + } while (true) + + if (!this.getIsAuthenticated()) { + return false + } + + // this._page.on('response', this._onResponse.bind(this)) + return true + } + + // _onResponse = (response: HTTPResponse) => { + // const request = response.request() + + // console.log('response', { + // url: response.url(), + // ok: response.ok(), + // status: response.status(), + // statusText: response.statusText(), + // headers: response.headers(), + // request: { + // method: request.method(), + // headers: request.headers() + // } + // }) + // } + + async getIsAuthenticated() { + try { + const inputBox = await this._getInputBox() + return !!inputBox + } catch (err) { + // can happen when navigating during login + return false + } + } + + async getLastMessage(): Promise { + const messages = await this.getMessages() + + if (messages) { + return messages[messages.length - 1] + } else { + return null + } + } + + async getPrompts(): Promise { + // Get all prompts + const messages = await this._page.$$( + '.text-base:has(.whitespace-pre-wrap):not(:has(button:nth-child(2))) .whitespace-pre-wrap' + ) + + // Prompts are always plaintext + return Promise.all(messages.map((a) => a.evaluate((el) => el.textContent))) + } + + async getMessages(): Promise { + // Get all complete messages + // (in-progress messages that are being streamed back don't contain action buttons) + const messages = await this._page.$$( + '.text-base:has(.whitespace-pre-wrap):has(button:nth-child(2)) .whitespace-pre-wrap' + ) + + if (this._markdown) { + const htmlMessages = await Promise.all( + messages.map((a) => a.evaluate((el) => el.innerHTML)) + ) + + const markdownMessages = htmlMessages.map((messageHtml) => { + // parse markdown from message HTML + messageHtml = messageHtml.replace('Copy code', '') + return html2md(messageHtml, { + ignoreTags: [ + 'button', + 'svg', + 'style', + 'form', + 'noscript', + 'script', + 'meta', + 'head' + ], + skipTags: ['button', 'svg'] + }) + }) + + return markdownMessages + } else { + // plaintext + const plaintextMessages = await Promise.all( + messages.map((a) => a.evaluate((el) => el.textContent)) + ) + return plaintextMessages + } + } + + async sendMessage(message: string): Promise { + const inputBox = await this._getInputBox() + if (!inputBox) throw new Error('not signed in') + + const lastMessage = await this.getLastMessage() + + await inputBox.click() + await inputBox.type(message, { delay: 0 }) + await inputBox.press('Enter') + + do { + await delay(1000) + + // TODO: this logic needs some work because we can have repeat messages... + const newLastMessage = await this.getLastMessage() + if ( + newLastMessage && + lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase() + ) { + return newLastMessage + } + } while (true) + } + + async resetThread() { + const resetButton = await this._page.$('nav > a:nth-child(1)') + if (!resetButton) throw new Error('not signed in') + + await resetButton.click() + } + + async close() { + await this._browser.close() + this._page = null + this._browser = null + } + + protected async _getInputBox() { + // [data-id="root"] + return this._page.$('textarea') + } +} diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index fea5af6..ab4f71d 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -95,6 +95,7 @@ export class ChatGPTAPI { 'user-agent': this._userAgent, 'x-openai-assistant-app-id': '', 'accept-language': 'en-US,en;q=0.9', + 'accept-encoding': 'gzip, deflate, br', origin: 'https://chat.openai.com', referer: 'https://chat.openai.com/chat', 'sec-ch-ua': @@ -299,6 +300,45 @@ export class ChatGPTAPI { } } + async sendModeration(input: string) { + const accessToken = await this.refreshAccessToken() + const url = `${this._backendApiBaseUrl}/moderations` + const headers = { + ...this._headers, + Authorization: `Bearer ${accessToken}`, + Accept: '*/*', + 'Content-Type': 'application/json', + Cookie: `cf_clearance=${this._clearanceToken}` + } + + const body: types.ModerationsJSONBody = { + input, + model: 'text-moderation-playground' + } + + if (this._debug) { + console.log('POST', url, headers, body) + } + + const res = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body) + }).then((r) => { + if (!r.ok) { + const error = new types.ChatGPTError(`${r.status} ${r.statusText}`) + error.response = r + error.statusCode = r.status + error.statusText = r.statusText + throw error + } + + return r.json() as any as types.ModerationsJSONResult + }) + + return res + } + /** * @returns `true` if the client has a valid acces token or `false` if refreshing * the token fails. diff --git a/src/index.ts b/src/index.ts index 976d160..ab2ab04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './chatgpt-api' +export * from './chatgpt-api-browser' export * from './chatgpt-conversation' export * from './types' export * from './utils' diff --git a/src/openai-auth.ts b/src/openai-auth.ts index 121fd42..7ece9b0 100644 --- a/src/openai-auth.ts +++ b/src/openai-auth.ts @@ -10,12 +10,15 @@ import { type PuppeteerLaunchOptions } from 'puppeteer' import puppeteer from 'puppeteer-extra' +import RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha' import StealthPlugin from 'puppeteer-extra-plugin-stealth' import * as types from './types' puppeteer.use(StealthPlugin()) +let hasRecaptchaPlugin = false + /** * Represents everything that's required to pass into `ChatGPTAPI` in order * to authenticate with the unofficial ChatGPT API. @@ -46,47 +49,64 @@ export async function getOpenAIAuth({ email, password, browser, + page, timeoutMs = 2 * 60 * 1000, - isGoogleLogin = false + // TODO: temporary for testing... + // timeoutMs = 60 * 60 * 1000, + isGoogleLogin = false, + captchaToken = process.env.CAPTCHA_TOKEN }: { email?: string password?: string browser?: Browser + page?: Page timeoutMs?: number isGoogleLogin?: boolean + captchaToken?: string }): Promise { - let page: Page - let origBrowser = browser + const origBrowser = browser + const origPage = page try { if (!browser) { - browser = await getBrowser() + browser = await getBrowser({ captchaToken }) } const userAgent = await browser.userAgent() - page = (await browser.pages())[0] || (await browser.newPage()) - page.setDefaultTimeout(timeoutMs) + if (!page) { + page = (await browser.pages())[0] || (await browser.newPage()) + page.setDefaultTimeout(timeoutMs) + } - await page.goto('https://chat.openai.com/auth/login') + await page.goto('https://chat.openai.com/auth/login', { + waitUntil: 'networkidle0' + }) // NOTE: this is where you may encounter a CAPTCHA + if (hasRecaptchaPlugin) { + await page.solveRecaptchas() + } + await checkForChatGPTAtCapacity(page) - await page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs }) - - // once we get to this point, the Cloudflare cookies are available - await delay(1000) + // once we get to this point, the Cloudflare cookies should be available // login as well (optional) if (email && password) { + await page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs }) + await delay(500) + await Promise.all([ + // click login button page.click('#__next .btn-primary'), page.waitForNavigation({ waitUntil: 'networkidle0' }) ]) - let submitP: Promise + await checkForChatGPTAtCapacity(page) + + let submitP: () => Promise if (isGoogleLogin) { await page.click('button[data-provider="google"]') @@ -98,19 +118,25 @@ export async function getOpenAIAuth({ ]) await page.waitForSelector('input[type="password"]', { visible: true }) await page.type('input[type="password"]', password, { delay: 10 }) - submitP = page.keyboard.press('Enter') + submitP = () => page.keyboard.press('Enter') } else { await page.waitForSelector('#username') - await page.type('#username', email, { delay: 10 }) + await page.type('#username', email, { delay: 20 }) + await delay(100) + + if (hasRecaptchaPlugin) { + console.log('solveRecaptchas()') + const res = await page.solveRecaptchas() + console.log('solveRecaptchas result', res) + } + await page.click('button[type="submit"]') await page.waitForSelector('#password') await page.type('#password', password, { delay: 10 }) - submitP = page.click('button[type="submit"]') + submitP = () => page.click('button[type="submit"]') } await Promise.all([ - submitP, - new Promise((resolve, reject) => { let resolved = false @@ -151,7 +177,9 @@ export async function getOpenAIAuth({ }) setTimeout(waitForCapacityText, 500) - }) + }), + + submitP() ]) } @@ -170,11 +198,10 @@ export async function getOpenAIAuth({ return authInfo } catch (err) { - console.error(err) throw err } finally { if (origBrowser) { - if (page) { + if (page && page !== origPage) { await page.close() } } else if (browser) { @@ -191,7 +218,28 @@ export async function getOpenAIAuth({ * able to use the built-in `puppeteer` version of Chromium because Cloudflare * recognizes it and blocks access. */ -export async function getBrowser(launchOptions?: PuppeteerLaunchOptions) { +export async function getBrowser( + opts: PuppeteerLaunchOptions & { + captchaToken?: string + } = {} +) { + const { captchaToken = process.env.CAPTCHA_TOKEN, ...launchOptions } = opts + + if (captchaToken && !hasRecaptchaPlugin) { + hasRecaptchaPlugin = true + console.log('use captcha', captchaToken) + + puppeteer.use( + RecaptchaPlugin({ + provider: { + id: '2captcha', + token: captchaToken + }, + visualFeedback: true // colorize reCAPTCHAs (violet = detected, green = solved) + }) + ) + } + return puppeteer.launch({ headless: false, args: ['--no-sandbox', '--exclude-switches', 'enable-automation'], @@ -212,16 +260,17 @@ export const defaultChromeExecutablePath = (): string => { case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' - default: + default: { /** - * Since two (2) separate chrome releases exists on linux - * we first do a check to ensure we're executing the right one. + * Since two (2) separate chrome releases exist 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' + } } } @@ -231,6 +280,12 @@ async function checkForChatGPTAtCapacity(page: Page) { try { // res = await page.$('[role="alert"]') res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]") + console.log('capacity', res) + + if (!res?.length) { + res = await page.$x("//div[contains(., 'at capacity right now')]") + console.log('capacity2', res) + } } catch (err) { // ignore errors likely due to navigation } From 7ddac692232839be94b75dcd133690bbef6ffa7e Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 00:22:31 -0600 Subject: [PATCH 03/12] feat: more WIP.. --- demos/demo.ts | 2 +- src/chatgpt-api-browser.ts | 105 +++++++++++++++++++++++++++++++------ src/openai-auth.ts | 12 ++--- src/utils.ts | 47 +++++++++++++++++ 4 files changed, 142 insertions(+), 24 deletions(-) diff --git a/demos/demo.ts b/demos/demo.ts index 0c18173..bdd59db 100644 --- a/demos/demo.ts +++ b/demos/demo.ts @@ -16,7 +16,7 @@ async function main() { const email = process.env.OPENAI_EMAIL const password = process.env.OPENAI_PASSWORD - const api = new ChatGPTAPIBrowser({ email, password }) + const api = new ChatGPTAPIBrowser({ email, password, debug: true }) const res = await api.init() console.log('init result', res) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index 377bf7a..f88b95c 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -1,9 +1,10 @@ import delay from 'delay' import html2md from 'html-to-md' -import { type Browser, type HTTPResponse, type Page } from 'puppeteer' +import type { Browser, HTTPRequest, HTTPResponse, Page } from 'puppeteer' import * as types from './types' import { getBrowser, getOpenAIAuth } from './openai-auth' +import { isRelevantRequest, minimizePage } from './utils' export class ChatGPTAPIBrowser { protected _markdown: boolean @@ -102,25 +103,93 @@ export class ChatGPTAPIBrowser { return false } - // this._page.on('response', this._onResponse.bind(this)) + // await minimizePage(this._page) + + this._page.on('request', this._onRequest.bind(this)) + this._page.on('response', this._onResponse.bind(this)) + return true } - // _onResponse = (response: HTTPResponse) => { - // const request = response.request() + _onRequest = (request: HTTPRequest) => { + if (!this._debug) return - // console.log('response', { - // url: response.url(), - // ok: response.ok(), - // status: response.status(), - // statusText: response.statusText(), - // headers: response.headers(), - // request: { - // method: request.method(), - // headers: request.headers() - // } - // }) - // } + const url = request.url() + if (!isRelevantRequest(url)) { + return + } + + const method = request.method() + let body: any + + if (method === 'POST') { + body = request.postData() + + try { + body = JSON.parse(body) + } catch (_) {} + + // if (url.endsWith('/conversation') && typeof body === 'object') { + // const conversationBody: types.ConversationJSONBody = body + // const conversationId = conversationBody.conversation_id + // const parentMessageId = conversationBody.parent_message_id + // const messageId = conversationBody.messages?.[0]?.id + // const prompt = conversationBody.messages?.[0]?.content?.parts?.[0] + + // // TODO: store this info for the current sendMessage request + // } + } + + console.log('\nrequest', { + url, + method, + headers: request.headers(), + body + }) + } + + _onResponse = async (response: HTTPResponse) => { + if (!this._debug) return + + const request = response.request() + + const url = response.url() + if (!isRelevantRequest(url)) { + return + } + + let body: any + try { + body = await response.json() + } catch (_) {} + + if (url.endsWith('/conversation')) { + // const parser = createParser((event) => { + // if (event.type === 'event') { + // onMessage(event.data) + // } + // }) + // await response.buffer() + // for await (const chunk of streamAsyncIterable(response.body)) { + // const str = new TextDecoder().decode(chunk) + // parser.feed(str) + // } + } + + console.log('\nresponse', { + url, + ok: response.ok(), + status: response.status(), + statusText: response.statusText(), + headers: response.headers(), + body, + request: { + method: request.method(), + headers: request.headers(), + body: request.postData() + } + }) + } async getIsAuthenticated() { try { @@ -198,7 +267,8 @@ export class ChatGPTAPIBrowser { const lastMessage = await this.getLastMessage() - await inputBox.click() + message = message.replace('\n', '\t') + await inputBox.focus() await inputBox.type(message, { delay: 0 }) await inputBox.press('Enter') @@ -211,6 +281,7 @@ export class ChatGPTAPIBrowser { newLastMessage && lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase() ) { + await delay(5000) return newLastMessage } } while (true) diff --git a/src/openai-auth.ts b/src/openai-auth.ts index 7ece9b0..1006d55 100644 --- a/src/openai-auth.ts +++ b/src/openai-auth.ts @@ -2,12 +2,12 @@ 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 +import type { + Browser, + ElementHandle, + Page, + Protocol, + PuppeteerLaunchOptions } from 'puppeteer' import puppeteer from 'puppeteer-extra' import RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha' diff --git a/src/utils.ts b/src/utils.ts index 724f3b2..bb650ce 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import type { Page } from 'puppeteer' import { remark } from 'remark' import stripMarkdown from 'strip-markdown' @@ -7,3 +8,49 @@ export function markdownToText(markdown?: string): string { .processSync(markdown ?? '') .toString() } + +export async function minimizePage(page: Page) { + const session = await page.target().createCDPSession() + const goods = await session.send('Browser.getWindowForTarget') + const { windowId } = goods + await session.send('Browser.setWindowBounds', { + windowId, + bounds: { windowState: 'minimized' } + }) +} + +export async function maximizePage(page: Page) { + const session = await page.target().createCDPSession() + const goods = await session.send('Browser.getWindowForTarget') + const { windowId } = goods + await session.send('Browser.setWindowBounds', { + windowId, + bounds: { windowState: 'normal' } + }) +} + +export function isRelevantRequest(url: string): boolean { + let pathname + + try { + const parsedUrl = new URL(url) + pathname = parsedUrl.pathname + url = parsedUrl.toString() + } catch (_) { + return false + } + + if (!url.startsWith('https://chat.openai.com')) { + return false + } + + if (pathname.startsWith('/_next')) { + return false + } + + if (pathname.endsWith('backend-api/moderations')) { + return false + } + + return true +} From 16d144adab6cfa1225ed9e9ce3d7324c13101a30 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 14 Dec 2022 22:24:03 -0800 Subject: [PATCH 04/12] fix: properly handle messages with newlines Pressing Enter would normally send the current message, so now the code splits it up by newlines and uses Shift+Enter unless it's the last split. --- src/chatgpt-api-browser.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index 377bf7a..a91c8bd 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -199,8 +199,17 @@ export class ChatGPTAPIBrowser { const lastMessage = await this.getLastMessage() await inputBox.click() - await inputBox.type(message, { delay: 0 }) - await inputBox.press('Enter') + const paragraphs = message.split('\n') + for (let i = 0; i < paragraphs.length; i++) { + await inputBox.type(paragraphs[i], { delay: 0 }) + if (i < paragraphs.length - 1) { + await this._page.keyboard.down('Shift') + await inputBox.press('Enter') + await this._page.keyboard.up('Shift') + } else { + await inputBox.press('Enter') + } + } do { await delay(1000) From 0d09ca965f8898ef6ec3b5529fac0f4963606a97 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 00:44:13 -0600 Subject: [PATCH 05/12] feat: handle session expiration gracefully --- src/chatgpt-api-browser.ts | 86 ++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index 2ca4c9b..d626358 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -2,9 +2,8 @@ import delay from 'delay' import html2md from 'html-to-md' import type { Browser, HTTPRequest, HTTPResponse, Page } from 'puppeteer' -import * as types from './types' import { getBrowser, getOpenAIAuth } from './openai-auth' -import { isRelevantRequest, minimizePage } from './utils' +import { isRelevantRequest, maximizePage, minimizePage } from './utils' export class ChatGPTAPIBrowser { protected _markdown: boolean @@ -103,7 +102,7 @@ export class ChatGPTAPIBrowser { return false } - // await minimizePage(this._page) + await minimizePage(this._page) this._page.on('request', this._onRequest.bind(this)) this._page.on('response', this._onResponse.bind(this)) @@ -112,8 +111,6 @@ export class ChatGPTAPIBrowser { } _onRequest = (request: HTTPRequest) => { - if (!this._debug) return - const url = request.url() if (!isRelevantRequest(url)) { return @@ -140,17 +137,17 @@ export class ChatGPTAPIBrowser { // } } - console.log('\nrequest', { - url, - method, - headers: request.headers(), - body - }) + if (this._debug) { + console.log('\nrequest', { + url, + method, + headers: request.headers(), + body + }) + } } _onResponse = async (response: HTTPResponse) => { - if (!this._debug) return - const request = response.request() const url = response.url() @@ -158,37 +155,54 @@ export class ChatGPTAPIBrowser { return } + const status = response.status() + let body: any try { body = await response.json() } catch (_) {} - if (url.endsWith('/conversation')) { - // const parser = createParser((event) => { - // if (event.type === 'event') { - // onMessage(event.data) - // } - // }) - // await response.buffer() - // for await (const chunk of streamAsyncIterable(response.body)) { - // const str = new TextDecoder().decode(chunk) - // parser.feed(str) - // } + if (this._debug) { + console.log('\nresponse', { + url, + ok: response.ok(), + status, + statusText: response.statusText(), + headers: response.headers(), + body, + request: { + method: request.method(), + headers: request.headers(), + body: request.postData() + } + }) } - console.log('\nresponse', { - url, - ok: response.ok(), - status: response.status(), - statusText: response.statusText(), - headers: response.headers(), - body, - request: { - method: request.method(), - headers: request.headers(), - body: request.postData() + if (url.endsWith('/conversation')) { + if (status === 403) { + await this.handle403Error() } - }) + } else if (url.endsWith('api/auth/session')) { + if (status === 403) { + await this.handle403Error() + } + } + } + + async handle403Error() { + console.log(`ChatGPT "${this._email}" session expired; refreshing...`) + try { + await maximizePage(this._page) + await this._page.reload({ + waitUntil: 'networkidle0' + }) + await minimizePage(this._page) + } catch (err) { + console.error( + `ChatGPT "${this._email}" error refreshing session`, + err.toString() + ) + } } async getIsAuthenticated() { From 8e0f32b444667dc1ccd98d88a5d775dc4bb02c52 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 01:35:09 -0600 Subject: [PATCH 06/12] feat: add timeout support to browser sendMessage --- src/chatgpt-api-browser.ts | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index d626358..91d0e33 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -1,5 +1,6 @@ import delay from 'delay' import html2md from 'html-to-md' +import pTimeout from 'p-timeout' import type { Browser, HTTPRequest, HTTPResponse, Page } from 'puppeteer' import { getBrowser, getOpenAIAuth } from './openai-auth' @@ -275,7 +276,14 @@ export class ChatGPTAPIBrowser { } } - async sendMessage(message: string): Promise { + async sendMessage( + message: string, + opts: { + timeoutMs?: number + } = {} + ): Promise { + const { timeoutMs } = opts + const inputBox = await this._getInputBox() if (!inputBox) throw new Error('not signed in') @@ -294,19 +302,32 @@ export class ChatGPTAPIBrowser { } } - do { - await delay(1000) + const responseP = new Promise(async (resolve, reject) => { + try { + do { + await delay(1000) - // TODO: this logic needs some work because we can have repeat messages... - const newLastMessage = await this.getLastMessage() - if ( - newLastMessage && - lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase() - ) { - await delay(5000) - return newLastMessage + // TODO: this logic needs some work because we can have repeat messages... + const newLastMessage = await this.getLastMessage() + if ( + newLastMessage && + lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase() + ) { + return resolve(newLastMessage) + } + } while (true) + } catch (err) { + return reject(err) } - } while (true) + }) + + if (timeoutMs) { + return pTimeout(responseP, { + milliseconds: timeoutMs + }) + } else { + return responseP + } } async resetThread() { From b3f71bc1e47ac65360a9c8b05ee16ddb559d0d43 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 01:39:09 -0600 Subject: [PATCH 07/12] feat: bump reload timeout --- src/chatgpt-api-browser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index 91d0e33..a2e2016 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -195,7 +195,8 @@ export class ChatGPTAPIBrowser { try { await maximizePage(this._page) await this._page.reload({ - waitUntil: 'networkidle0' + waitUntil: 'networkidle0', + timeout: 2 * 60 * 1000 // 2 minutes }) await minimizePage(this._page) } catch (err) { From 1f1fc66429f6b833c1e1d369fe4b14e3a7bf9e8b Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 02:14:17 -0600 Subject: [PATCH 08/12] feat: close browser on error --- src/chatgpt-api-browser.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/chatgpt-api-browser.ts b/src/chatgpt-api-browser.ts index a2e2016..9e2df38 100644 --- a/src/chatgpt-api-browser.ts +++ b/src/chatgpt-api-browser.ts @@ -59,18 +59,29 @@ export class ChatGPTAPIBrowser { this._browser = null } - this._browser = await getBrowser({ captchaToken: this._captchaToken }) - this._page = - (await this._browser.pages())[0] || (await this._browser.newPage()) + try { + this._browser = await getBrowser({ captchaToken: this._captchaToken }) + this._page = + (await this._browser.pages())[0] || (await this._browser.newPage()) - // bypass cloudflare and login - await getOpenAIAuth({ - email: this._email, - password: this._password, - browser: this._browser, - page: this._page, - isGoogleLogin: this._isGoogleLogin - }) + // bypass cloudflare and login + await getOpenAIAuth({ + email: this._email, + password: this._password, + browser: this._browser, + page: this._page, + isGoogleLogin: this._isGoogleLogin + }) + } catch (err) { + if (this._browser) { + await this._browser.close() + } + + this._browser = null + this._page = null + + throw err + } const chatUrl = 'https://chat.openai.com/chat' const url = this._page.url().replace(/\/$/, '') From fdb60f633a90817edc1af11c914a00399a93cc5c Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 16:59:28 -0600 Subject: [PATCH 09/12] fix: minor cleanups --- src/openai-auth.ts | 143 ++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/src/openai-auth.ts b/src/openai-auth.ts index 1006d55..f2b9057 100644 --- a/src/openai-auth.ts +++ b/src/openai-auth.ts @@ -2,13 +2,7 @@ import * as fs from 'node:fs' import * as os from 'node:os' import delay from 'delay' -import type { - Browser, - ElementHandle, - Page, - Protocol, - PuppeteerLaunchOptions -} from 'puppeteer' +import type { Browser, Page, Protocol, PuppeteerLaunchOptions } from 'puppeteer' import puppeteer from 'puppeteer-extra' import RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha' import StealthPlugin from 'puppeteer-extra-plugin-stealth' @@ -51,8 +45,6 @@ export async function getOpenAIAuth({ browser, page, timeoutMs = 2 * 60 * 1000, - // TODO: temporary for testing... - // timeoutMs = 60 * 60 * 1000, isGoogleLogin = false, captchaToken = process.env.CAPTCHA_TOKEN }: { @@ -93,15 +85,18 @@ export async function getOpenAIAuth({ // login as well (optional) if (email && password) { - await page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs }) + await waitForConditionOrAtCapacity(page, () => + page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs }) + ) await delay(500) + // click login button and wait for navigation to finish await Promise.all([ - // click login button - page.click('#__next .btn-primary'), page.waitForNavigation({ waitUntil: 'networkidle0' - }) + }), + + page.click('#__next .btn-primary') ]) await checkForChatGPTAtCapacity(page) @@ -125,9 +120,9 @@ export async function getOpenAIAuth({ await delay(100) if (hasRecaptchaPlugin) { - console.log('solveRecaptchas()') + // console.log('solveRecaptchas()') const res = await page.solveRecaptchas() - console.log('solveRecaptchas result', res) + // console.log('solveRecaptchas result', res) } await page.click('button[type="submit"]') @@ -137,50 +132,16 @@ export async function getOpenAIAuth({ } await Promise.all([ - new Promise((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) - }), - + waitForConditionOrAtCapacity(page, () => + page.waitForNavigation({ + waitUntil: 'networkidle0' + }) + ), submitP() ]) + } else { + await delay(2000) + await checkForChatGPTAtCapacity(page) } const pageCookies = await page.cookies() @@ -227,7 +188,7 @@ export async function getBrowser( if (captchaToken && !hasRecaptchaPlugin) { hasRecaptchaPlugin = true - console.log('use captcha', captchaToken) + // console.log('use captcha', captchaToken) puppeteer.use( RecaptchaPlugin({ @@ -275,17 +236,18 @@ export const defaultChromeExecutablePath = (): string => { } async function checkForChatGPTAtCapacity(page: Page) { - let res: ElementHandle[] + // console.log('checkForChatGPTAtCapacity', page.url()) + let res: any[] try { - // res = await page.$('[role="alert"]') res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]") - console.log('capacity', res) - - if (!res?.length) { - res = await page.$x("//div[contains(., 'at capacity right now')]") - console.log('capacity2', res) - } + // console.log('capacity1', els) + // if (els?.length) { + // res = await Promise.all( + // els.map((a) => a.evaluate((el) => el.textContent)) + // ) + // console.log('capacity2', res) + // } } catch (err) { // ignore errors likely due to navigation } @@ -296,3 +258,52 @@ async function checkForChatGPTAtCapacity(page: Page) { throw error } } + +async function waitForConditionOrAtCapacity( + page: Page, + condition: () => Promise, + opts: { + pollingIntervalMs?: number + } = {} +) { + const { pollingIntervalMs = 500 } = opts + + return new Promise((resolve, reject) => { + let resolved = false + + async function waitForCapacityText() { + if (resolved) { + return + } + + try { + await checkForChatGPTAtCapacity(page) + + if (!resolved) { + setTimeout(waitForCapacityText, pollingIntervalMs) + } + } catch (err) { + if (!resolved) { + resolved = true + return reject(err) + } + } + } + + condition() + .then(() => { + if (!resolved) { + resolved = true + resolve() + } + }) + .catch((err) => { + if (!resolved) { + resolved = true + reject(err) + } + }) + + setTimeout(waitForCapacityText, pollingIntervalMs) + }) +} From 16d16993cdf548c1d541cff5f0bc249fac16f387 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 17:14:14 -0600 Subject: [PATCH 10/12] docs: update readme for browser-based solution status --- readme.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index ce4d240..65ab28f 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,31 @@ -# Update December 12, 2022 +# Update December 15, 2022 -Yesterday, OpenAI added additional Cloudflare protections that make it more difficult to access the unofficial API. +On December 11th, OpenAI added Cloudflare protections that make it more difficult to access the unofficial API. This package has been updated to use Puppeteer to automatically log in to ChatGPT and extract the necessary auth credentials. 🔥 +Even with this in place, however, it's not uncommon to run into 429 / 403 errors at the moment using the `getOpenAIAuth` + `ChatGPTAPI` approach. + +To circumvent these issues, we've also added a full browser-based solution, which uses Puppeteer to log into the webapp and fully automate the web UI. + +The full browser version is working consistently and can be used via: + +```ts +import { ChatGPTAPIBrowser } from 'chatgpt' + +const api = new ChatGPTAPIBrowser({ + email: process.env.OPENAI_EMAIL, + password: process.env.OPENAI_PASSWORD +}) +await api.init() + +const response = await api.sendMessage('Hello World!') +``` + +Note that this solution is not lightweight, but it does work a lot more consistently than the REST API-based versions. I'm currently using this solution to power 10 OpenAI accounts concurrently across 10 minimized Chrome windows for my [Twitter bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot). 😂 + +If you get a "ChatGPT is at capacity" error when logging in, note that this is also happening quite frequently on the official webapp. Their servers are overloaded, and we're all trying our best to offer access to this amazing technology. + To use the updated version, **make sure you're using the latest version of this package and Node.js >= 18**. Then update your code following the examples below, paying special attention to the sections on [Authentication](#authentication) and [Restrictions](#restrictions). We're working hard to improve this process (especially CAPTCHA automation). Keep in mind that this package will be updated to use the official API as soon as it's released, so things should get much easier over time. 💪 @@ -11,7 +33,8 @@ We're working hard to improve this process (especially CAPTCHA automation). Keep Lastly, please consider starring this repo and following me on twitter twitter to help support the project. Thanks && cheers, -Travis + +[Travis](https://twitter.com/transitive_bs) --- @@ -76,6 +99,25 @@ async function example() { } ``` +Or, if you want to try the full browser-based solution: + +```ts +import { ChatGPTAPIBrowser } from 'chatgpt' + +async function example() { + // use puppeteer to bypass cloudflare (headful because of captchas) + const api = new ChatGPTAPIBrowser({ + email: process.env.OPENAI_EMAIL, + password: process.env.OPENAI_PASSWORD + }) + + await api.init() + + const response = await api.sendMessage('Hello World!') + console.log(response) +} +``` + ChatGPT responses are formatted as markdown by default. If you want to work with plaintext instead, you can use: ```ts @@ -191,6 +233,8 @@ Pass `sessionToken`, `clearanceToken`, and `userAgent` to the `ChatGPTAPI` const ### Restrictions +These restrictions are for the `getOpenAIAuth` + `ChatGPTAPI` solution, which uses the unofficial API. The browser-based solution, `ChatGPTAPIBrowser`, doesn't have many of these restrictions, though you'll still have to manually bypass CAPTCHAs by hand. + **Please read carefully** - You must use `node >= 18` at the moment. I'm using `v19.2.0` in my testing. From 53464d02985a9166f60955e2f35ceb4fffc8705e Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 17:15:38 -0600 Subject: [PATCH 11/12] chore: update auto-generated docs --- docs/classes/ChatGPTAPI.md | 41 ++++-- docs/classes/ChatGPTAPIBrowser.md | 221 ++++++++++++++++++++++++++++ docs/classes/ChatGPTConversation.md | 10 +- docs/classes/ChatGPTError.md | 8 +- docs/modules.md | 142 ++++++++++++++---- docs/readme.md | 61 +++++++- readme.md | 2 +- 7 files changed, 429 insertions(+), 56 deletions(-) create mode 100644 docs/classes/ChatGPTAPIBrowser.md diff --git a/docs/classes/ChatGPTAPI.md b/docs/classes/ChatGPTAPI.md index 9dd2ddc..2d03a54 100644 --- a/docs/classes/ChatGPTAPI.md +++ b/docs/classes/ChatGPTAPI.md @@ -22,6 +22,7 @@ - [getIsAuthenticated](ChatGPTAPI.md#getisauthenticated) - [refreshAccessToken](ChatGPTAPI.md#refreshaccesstoken) - [sendMessage](ChatGPTAPI.md#sendmessage) +- [sendModeration](ChatGPTAPI.md#sendmoderation) ## Constructors @@ -52,7 +53,7 @@ to obtain your `clearanceToken`. #### Defined in -[src/chatgpt-api.ts:45](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L45) +[src/chatgpt-api.ts:45](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L45) ## Accessors @@ -68,7 +69,7 @@ Gets the current Cloudflare clearance token (`cf_clearance` cookie value). #### Defined in -[src/chatgpt-api.ts:136](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L136) +[src/chatgpt-api.ts:137](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L137) ___ @@ -84,7 +85,7 @@ Gets the current session token. #### Defined in -[src/chatgpt-api.ts:131](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L131) +[src/chatgpt-api.ts:132](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L132) ___ @@ -100,7 +101,7 @@ Gets the currently signed-in user, if authenticated, `null` otherwise. #### Defined in -[src/chatgpt-api.ts:126](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L126) +[src/chatgpt-api.ts:127](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L127) ___ @@ -116,7 +117,7 @@ Gets the current user agent. #### Defined in -[src/chatgpt-api.ts:141](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L141) +[src/chatgpt-api.ts:142](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L142) ## Methods @@ -133,7 +134,7 @@ is still valid. #### Defined in -[src/chatgpt-api.ts:319](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L319) +[src/chatgpt-api.ts:359](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L359) ___ @@ -160,7 +161,7 @@ The new conversation instance #### Defined in -[src/chatgpt-api.ts:425](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L425) +[src/chatgpt-api.ts:465](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L465) ___ @@ -177,7 +178,7 @@ the token fails. #### Defined in -[src/chatgpt-api.ts:306](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L306) +[src/chatgpt-api.ts:346](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L346) ___ @@ -203,7 +204,7 @@ A valid access token #### Defined in -[src/chatgpt-api.ts:333](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L333) +[src/chatgpt-api.ts:373](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L373) ___ @@ -234,4 +235,24 @@ The response from ChatGPT #### Defined in -[src/chatgpt-api.ts:166](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-api.ts#L166) +[src/chatgpt-api.ts:167](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L167) + +___ + +### sendModeration + +▸ **sendModeration**(`input`): `Promise`<[`ModerationsJSONResult`](../modules.md#moderationsjsonresult)\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `input` | `string` | + +#### Returns + +`Promise`<[`ModerationsJSONResult`](../modules.md#moderationsjsonresult)\> + +#### Defined in + +[src/chatgpt-api.ts:303](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api.ts#L303) diff --git a/docs/classes/ChatGPTAPIBrowser.md b/docs/classes/ChatGPTAPIBrowser.md new file mode 100644 index 0000000..124dcd2 --- /dev/null +++ b/docs/classes/ChatGPTAPIBrowser.md @@ -0,0 +1,221 @@ +[chatgpt](../readme.md) / [Exports](../modules.md) / ChatGPTAPIBrowser + +# Class: ChatGPTAPIBrowser + +## Table of contents + +### Constructors + +- [constructor](ChatGPTAPIBrowser.md#constructor) + +### Methods + +- [\_onRequest](ChatGPTAPIBrowser.md#_onrequest) +- [\_onResponse](ChatGPTAPIBrowser.md#_onresponse) +- [close](ChatGPTAPIBrowser.md#close) +- [getIsAuthenticated](ChatGPTAPIBrowser.md#getisauthenticated) +- [getLastMessage](ChatGPTAPIBrowser.md#getlastmessage) +- [getMessages](ChatGPTAPIBrowser.md#getmessages) +- [getPrompts](ChatGPTAPIBrowser.md#getprompts) +- [handle403Error](ChatGPTAPIBrowser.md#handle403error) +- [init](ChatGPTAPIBrowser.md#init) +- [resetThread](ChatGPTAPIBrowser.md#resetthread) +- [sendMessage](ChatGPTAPIBrowser.md#sendmessage) + +## Constructors + +### constructor + +• **new ChatGPTAPIBrowser**(`opts`) + +Creates a new client wrapper for automating the ChatGPT webapp. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `opts` | `Object` | - | +| `opts.captchaToken?` | `string` | - | +| `opts.debug?` | `boolean` | **`Default Value`** `false` * | +| `opts.email` | `string` | - | +| `opts.isGoogleLogin?` | `boolean` | - | +| `opts.markdown?` | `boolean` | **`Default Value`** `true` * | +| `opts.password` | `string` | - | + +#### Defined in + +[src/chatgpt-api-browser.ts:24](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L24) + +## Methods + +### \_onRequest + +▸ **_onRequest**(`request`): `void` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `request` | `HTTPRequest` | + +#### Returns + +`void` + +#### Defined in + +[src/chatgpt-api-browser.ts:125](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L125) + +___ + +### \_onResponse + +▸ **_onResponse**(`response`): `Promise`<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `response` | `HTTPResponse` | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:162](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L162) + +___ + +### close + +▸ **close**(): `Promise`<`void`\> + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:352](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L352) + +___ + +### getIsAuthenticated + +▸ **getIsAuthenticated**(): `Promise`<`boolean`\> + +#### Returns + +`Promise`<`boolean`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:221](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L221) + +___ + +### getLastMessage + +▸ **getLastMessage**(): `Promise`<`string`\> + +#### Returns + +`Promise`<`string`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:231](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L231) + +___ + +### getMessages + +▸ **getMessages**(): `Promise`<`string`[]\> + +#### Returns + +`Promise`<`string`[]\> + +#### Defined in + +[src/chatgpt-api-browser.ts:251](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L251) + +___ + +### getPrompts + +▸ **getPrompts**(): `Promise`<`string`[]\> + +#### Returns + +`Promise`<`string`[]\> + +#### Defined in + +[src/chatgpt-api-browser.ts:241](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L241) + +___ + +### handle403Error + +▸ **handle403Error**(): `Promise`<`void`\> + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:204](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L204) + +___ + +### init + +▸ **init**(): `Promise`<`boolean`\> + +#### Returns + +`Promise`<`boolean`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:55](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L55) + +___ + +### resetThread + +▸ **resetThread**(): `Promise`<`void`\> + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:345](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L345) + +___ + +### sendMessage + +▸ **sendMessage**(`message`, `opts?`): `Promise`<`string`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `message` | `string` | +| `opts` | `Object` | +| `opts.timeoutMs?` | `number` | + +#### Returns + +`Promise`<`string`\> + +#### Defined in + +[src/chatgpt-api-browser.ts:291](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-api-browser.ts#L291) diff --git a/docs/classes/ChatGPTConversation.md b/docs/classes/ChatGPTConversation.md index 8c2e46c..c117bf0 100644 --- a/docs/classes/ChatGPTConversation.md +++ b/docs/classes/ChatGPTConversation.md @@ -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/c257286/src/chatgpt-conversation.ts#L21) +[src/chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/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/c257286/src/chatgpt-conversation.ts#L10) +[src/chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-conversation.ts#L10) ___ @@ -61,7 +61,7 @@ ___ #### Defined in -[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L11) +[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-conversation.ts#L11) ___ @@ -71,7 +71,7 @@ ___ #### Defined in -[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/chatgpt-conversation.ts#L12) +[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/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/c257286/src/chatgpt-conversation.ts#L48) +[src/chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/chatgpt-conversation.ts#L48) diff --git a/docs/classes/ChatGPTError.md b/docs/classes/ChatGPTError.md index 25f3a69..4360ddc 100644 --- a/docs/classes/ChatGPTError.md +++ b/docs/classes/ChatGPTError.md @@ -66,7 +66,7 @@ node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib/lib.es2022.error #### Defined in -[src/types.ts:298](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L298) +[src/types.ts:298](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L298) ___ @@ -76,7 +76,7 @@ ___ #### Defined in -[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L297) +[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L297) ___ @@ -86,7 +86,7 @@ ___ #### Defined in -[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L295) +[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L295) ___ @@ -96,4 +96,4 @@ ___ #### Defined in -[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L296) +[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L296) diff --git a/docs/modules.md b/docs/modules.md index 12ade79..425dc21 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -7,6 +7,7 @@ ### Classes - [ChatGPTAPI](classes/ChatGPTAPI.md) +- [ChatGPTAPIBrowser](classes/ChatGPTAPIBrowser.md) - [ChatGPTConversation](classes/ChatGPTConversation.md) - [ChatGPTError](classes/ChatGPTError.md) @@ -39,9 +40,13 @@ ### Functions +- [defaultChromeExecutablePath](modules.md#defaultchromeexecutablepath) - [getBrowser](modules.md#getbrowser) - [getOpenAIAuth](modules.md#getopenaiauth) +- [isRelevantRequest](modules.md#isrelevantrequest) - [markdownToText](modules.md#markdowntotext) +- [maximizePage](modules.md#maximizepage) +- [minimizePage](modules.md#minimizepage) ## Type Aliases @@ -51,7 +56,7 @@ #### Defined in -[src/types.ts:109](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L109) +[src/types.ts:109](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L109) ___ @@ -61,7 +66,7 @@ ___ #### Defined in -[src/types.ts:1](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L1) +[src/types.ts:1](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L1) ___ @@ -83,7 +88,7 @@ https://chat.openapi.com/backend-api/conversation #### Defined in -[src/types.ts:134](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L134) +[src/types.ts:134](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L134) ___ @@ -101,7 +106,7 @@ ___ #### Defined in -[src/types.ts:251](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L251) +[src/types.ts:251](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L251) ___ @@ -126,7 +131,7 @@ ___ #### Defined in -[src/types.ts:257](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L257) +[src/types.ts:257](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L257) ___ @@ -136,7 +141,7 @@ ___ #### Defined in -[src/types.ts:276](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L276) +[src/types.ts:276](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L276) ___ @@ -153,7 +158,7 @@ ___ #### Defined in -[src/types.ts:270](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L270) +[src/types.ts:270](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L270) ___ @@ -175,7 +180,7 @@ https://chat.openapi.com/backend-api/conversation/message_feedback #### Defined in -[src/types.ts:193](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L193) +[src/types.ts:193](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L193) ___ @@ -185,7 +190,7 @@ ___ #### Defined in -[src/types.ts:249](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L249) +[src/types.ts:249](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L249) ___ @@ -205,7 +210,7 @@ ___ #### Defined in -[src/types.ts:222](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L222) +[src/types.ts:222](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L222) ___ @@ -215,7 +220,7 @@ ___ #### Defined in -[src/types.ts:220](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L220) +[src/types.ts:220](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L220) ___ @@ -225,7 +230,7 @@ ___ #### Defined in -[src/types.ts:275](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L275) +[src/types.ts:275](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L275) ___ @@ -243,7 +248,7 @@ ___ #### Defined in -[src/types.ts:77](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L77) +[src/types.ts:77](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L77) ___ @@ -261,7 +266,7 @@ https://chat.openapi.com/backend-api/models #### Defined in -[src/types.ts:70](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L70) +[src/types.ts:70](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L70) ___ @@ -280,7 +285,7 @@ https://chat.openapi.com/backend-api/moderations #### Defined in -[src/types.ts:97](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L97) +[src/types.ts:97](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L97) ___ @@ -300,7 +305,7 @@ https://chat.openapi.com/backend-api/moderations #### Defined in -[src/types.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L114) +[src/types.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L114) ___ @@ -322,7 +327,7 @@ to authenticate with the unofficial ChatGPT API. #### Defined in -[src/openai-auth.ts:17](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/openai-auth.ts#L17) +[src/openai-auth.ts:20](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/openai-auth.ts#L20) ___ @@ -340,7 +345,7 @@ ___ #### Defined in -[src/types.ts:161](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L161) +[src/types.ts:161](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L161) ___ @@ -357,7 +362,7 @@ ___ #### Defined in -[src/types.ts:178](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L178) +[src/types.ts:178](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L178) ___ @@ -367,7 +372,7 @@ ___ #### Defined in -[src/types.ts:3](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L3) +[src/types.ts:3](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L3) ___ @@ -377,7 +382,7 @@ ___ #### Defined in -[src/types.ts:289](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L289) +[src/types.ts:289](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L289) ___ @@ -400,7 +405,7 @@ ___ #### Defined in -[src/types.ts:278](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L278) +[src/types.ts:278](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L278) ___ @@ -421,7 +426,7 @@ https://chat.openapi.com/api/auth/session #### Defined in -[src/types.ts:8](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L8) +[src/types.ts:8](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L8) ___ @@ -443,13 +448,29 @@ ___ #### Defined in -[src/types.ts:30](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/types.ts#L30) +[src/types.ts:30](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/types.ts#L30) ## Functions +### defaultChromeExecutablePath + +▸ **defaultChromeExecutablePath**(): `string` + +Gets the default path to chrome's executable for the current platform. + +#### Returns + +`string` + +#### Defined in + +[src/openai-auth.ts:216](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/openai-auth.ts#L216) + +___ + ### getBrowser -▸ **getBrowser**(`launchOptions?`): `Promise`<`Browser`\> +▸ **getBrowser**(`opts?`): `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 @@ -459,7 +480,7 @@ recognizes it and blocks access. | Name | Type | | :------ | :------ | -| `launchOptions?` | `PuppeteerLaunchOptions` | +| `opts` | `PuppeteerLaunchOptions` & { `captchaToken?`: `string` } | #### Returns @@ -467,7 +488,7 @@ recognizes it and blocks access. #### Defined in -[src/openai-auth.ts:127](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/openai-auth.ts#L127) +[src/openai-auth.ts:182](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/openai-auth.ts#L182) ___ @@ -495,7 +516,10 @@ with your updated credentials. | :------ | :------ | | `__namedParameters` | `Object` | | `__namedParameters.browser?` | `Browser` | +| `__namedParameters.captchaToken?` | `string` | | `__namedParameters.email?` | `string` | +| `__namedParameters.isGoogleLogin?` | `boolean` | +| `__namedParameters.page?` | `Page` | | `__namedParameters.password?` | `string` | | `__namedParameters.timeoutMs?` | `number` | @@ -505,7 +529,27 @@ with your updated credentials. #### Defined in -[src/openai-auth.ts:39](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/openai-auth.ts#L39) +[src/openai-auth.ts:42](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/openai-auth.ts#L42) + +___ + +### isRelevantRequest + +▸ **isRelevantRequest**(`url`): `boolean` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `url` | `string` | + +#### Returns + +`boolean` + +#### Defined in + +[src/utils.ts:32](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/utils.ts#L32) ___ @@ -525,4 +569,44 @@ ___ #### Defined in -[src/utils.ts:4](https://github.com/transitive-bullshit/chatgpt-api/blob/c257286/src/utils.ts#L4) +[src/utils.ts:5](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/utils.ts#L5) + +___ + +### maximizePage + +▸ **maximizePage**(`page`): `Promise`<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `page` | `Page` | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/utils.ts:22](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/utils.ts#L22) + +___ + +### minimizePage + +▸ **minimizePage**(`page`): `Promise`<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `page` | `Page` | + +#### Returns + +`Promise`<`void`\> + +#### Defined in + +[src/utils.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/16d1699/src/utils.ts#L12) diff --git a/docs/readme.md b/docs/readme.md index dcdd5c7..7084903 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,11 +1,33 @@ chatgpt / [Exports](modules.md) -# Update December 12, 2022 +# Update December 15, 2022 -Yesterday, OpenAI added additional Cloudflare protections that make it more difficult to access the unofficial API. +On December 11th, OpenAI added Cloudflare protections that make it more difficult to access the unofficial API. This package has been updated to use Puppeteer to automatically log in to ChatGPT and extract the necessary auth credentials. 🔥 +Even with this in place, however, it's not uncommon to run into 429 / 403 errors at the moment using the `getOpenAIAuth` + `ChatGPTAPI` approach. + +To circumvent these issues, we've also added a full browser-based solution, which uses Puppeteer to log into the webapp and fully automate the web UI. + +The full browser version is working consistently and can be used via: + +```ts +import { ChatGPTAPIBrowser } from 'chatgpt' + +const api = new ChatGPTAPIBrowser({ + email: process.env.OPENAI_EMAIL, + password: process.env.OPENAI_PASSWORD +}) +await api.init() + +const response = await api.sendMessage('Hello World!') +``` + +Note that this solution is not lightweight, but it does work a lot more consistently than the REST API-based versions. I'm currently using this solution to power 10 OpenAI accounts concurrently across 10 minimized Chrome windows for my [Twitter bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot). 😂 + +If you get a "ChatGPT is at capacity" error when logging in, note that this is also happening quite frequently on the official webapp. Their servers are overloaded, and we're all trying our best to offer access to this amazing technology. + To use the updated version, **make sure you're using the latest version of this package and Node.js >= 18**. Then update your code following the examples below, paying special attention to the sections on [Authentication](#authentication) and [Restrictions](#restrictions). We're working hard to improve this process (especially CAPTCHA automation). Keep in mind that this package will be updated to use the official API as soon as it's released, so things should get much easier over time. 💪 @@ -13,7 +35,8 @@ We're working hard to improve this process (especially CAPTCHA automation). Keep Lastly, please consider starring this repo and following me on twitter twitter to help support the project. Thanks && cheers, -Travis + +[Travis](https://twitter.com/transitive_bs) --- @@ -33,7 +56,7 @@ Travis - [Docs](#docs) - [Demos](#demos) - [Authentication](#authentication) - - [Restrictions](#restrictions) + - [Restrictions](#restrictions) - [Projects](#projects) - [Compatibility](#compatibility) - [Credits](#credits) @@ -78,6 +101,25 @@ async function example() { } ``` +Or, if you want to try the full browser-based solution: + +```ts +import { ChatGPTAPIBrowser } from 'chatgpt' + +async function example() { + // use puppeteer to bypass cloudflare (headful because of captchas) + const api = new ChatGPTAPIBrowser({ + email: process.env.OPENAI_EMAIL, + password: process.env.OPENAI_PASSWORD + }) + + await api.init() + + const response = await api.sendMessage('Hello World!') + console.log(response) +} +``` + ChatGPT responses are formatted as markdown by default. If you want to work with plaintext instead, you can use: ```ts @@ -191,9 +233,11 @@ Pass `sessionToken`, `clearanceToken`, and `userAgent` to the `ChatGPTAPI` const > **Note** > This package will switch to using the official API once it's released, which will make this process much simpler. -#### Restrictions +### Restrictions -**Please read these carefully** +These restrictions are for the `getOpenAIAuth` + `ChatGPTAPI` solution, which uses the unofficial API. The browser-based solution, `ChatGPTAPIBrowser`, doesn't have many of these restrictions, though you'll still have to manually bypass CAPTCHAs by hand. + +**Please read carefully** - You must use `node >= 18` at the moment. I'm using `v19.2.0` in my testing. - Cloudflare `cf_clearance` **tokens expire after 2 hours**, so right now we recommend that you refresh your `cf_clearance` token every hour or so. @@ -211,10 +255,12 @@ All of these awesome projects are built using the `chatgpt` package. 🤯 - [Twitter Bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot) powered by ChatGPT ✨ - Mention [@ChatGPTBot](https://twitter.com/ChatGPTBot) on Twitter with your prompt to try it out +- [Lovelines.xyz](https://lovelines.xyz?ref=chatgpt-api) - [Chrome Extension](https://github.com/gragland/chatgpt-everywhere) ([demo](https://twitter.com/gabe_ragland/status/1599466486422470656)) - [VSCode Extension #1](https://github.com/mpociot/chatgpt-vscode) ([demo](https://twitter.com/marcelpociot/status/1599180144551526400), [updated version](https://github.com/timkmecl/chatgpt-vscode), [marketplace](https://marketplace.visualstudio.com/items?itemName=timkmecl.chatgpt)) - [VSCode Extension #2](https://github.com/barnesoir/chatgpt-vscode-plugin) ([marketplace](https://marketplace.visualstudio.com/items?itemName=JayBarnes.chatgpt-vscode-plugin)) - [VSCode Extension #3](https://github.com/gencay/vscode-chatgpt) ([marketplace](https://marketplace.visualstudio.com/items?itemName=gencay.vscode-chatgpt)) +- [VSCode Extension #4](https://github.com/dogukanakkaya/chatgpt-code-vscode-extension) ([marketplace](https://marketplace.visualstudio.com/items?itemName=dogukanakkaya.chatgpt-code)) - [Raycast Extension #1](https://github.com/abielzulio/chatgpt-raycast) ([demo](https://twitter.com/abielzulio/status/1600176002042191875)) - [Raycast Extension #2](https://github.com/domnantas/raycast-chatgpt) - [Telegram Bot #1](https://github.com/realies/chatgpt-telegram-bot) @@ -236,7 +282,6 @@ All of these awesome projects are built using the `chatgpt` package. 🤯 - [QQ Bot (oicq)](https://github.com/easydu2002/chat_gpt_oicq) - [QQ Bot (oicq + RabbitMQ)](https://github.com/linsyking/ChatGPT-QQBot) - [QQ Bot (go-cqhttp)](https://github.com/PairZhu/ChatGPT-QQRobot) -- [Lovelines.xyz](https://lovelines.xyz) - [EXM smart contracts](https://github.com/decentldotland/molecule) - [Flutter ChatGPT API](https://github.com/coskuncay/flutter_chatgpt_api) - [Carik Bot](https://github.com/luridarmawan/Carik) @@ -249,6 +294,8 @@ All of these awesome projects are built using the `chatgpt` package. 🤯 - [Assistant CLI](https://github.com/diciaup/assistant-cli) - [Teams Bot](https://github.com/formulahendry/chatgpt-teams-bot) - [Askai](https://github.com/yudax42/askai) +- [TalkGPT](https://github.com/ShadovvBeast/TalkGPT) +- [iOS Shortcut](https://github.com/leecobaby/shortcuts/blob/master/other/ChatGPT_EN.md) If you create a cool integration, feel free to open a PR and add it to the list. diff --git a/readme.md b/readme.md index 65ab28f..f3ace54 100644 --- a/readme.md +++ b/readme.md @@ -179,7 +179,7 @@ async function example() { ### Docs -See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods and parameters. +See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods and parameters. Here are the [docs](./docs/classes/ChatGPTAPIBrowser.md) for the browser-based version. ### Demos From 0d3ca08c7880e92bad90273c2dfaa802d7a990ed Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 Dec 2022 17:17:37 -0600 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=8D=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index f3ace54..bacf642 100644 --- a/readme.md +++ b/readme.md @@ -235,6 +235,8 @@ Pass `sessionToken`, `clearanceToken`, and `userAgent` to the `ChatGPTAPI` const These restrictions are for the `getOpenAIAuth` + `ChatGPTAPI` solution, which uses the unofficial API. The browser-based solution, `ChatGPTAPIBrowser`, doesn't have many of these restrictions, though you'll still have to manually bypass CAPTCHAs by hand. +Note: currently `ChatGPTAPIBrowser` doesn't support continuing arbitrary conversations based on `conversationId`. You can only continue conversations in the current tab or start new conversations using the `resetThread()` function. + **Please read carefully** - You must use `node >= 18` at the moment. I'm using `v19.2.0` in my testing.