From eaa4357a6cc364d807e3427aabbfd38bc55d1e7a Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Sat, 3 Dec 2022 02:46:57 -0600 Subject: [PATCH] fix: initial login flow bugs --- docs/classes/ChatGPTAPI.md | 16 ++++++------ docs/readme.md | 52 ++++++++++++++++++++++---------------- readme.md | 2 +- src/chatgpt-api.ts | 31 +++++++++++++++-------- src/example.ts | 22 +++++++++------- 5 files changed, 73 insertions(+), 50 deletions(-) diff --git a/docs/classes/ChatGPTAPI.md b/docs/classes/ChatGPTAPI.md index bb9d9726f..78c0d1ac6 100644 --- a/docs/classes/ChatGPTAPI.md +++ b/docs/classes/ChatGPTAPI.md @@ -36,7 +36,7 @@ #### Defined in -[chatgpt-api.ts:20](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L20) +[chatgpt-api.ts:20](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L20) ## Methods @@ -50,7 +50,7 @@ #### Defined in -[chatgpt-api.ts:175](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L175) +[chatgpt-api.ts:186](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L186) ___ @@ -64,7 +64,7 @@ ___ #### Defined in -[chatgpt-api.ts:88](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L88) +[chatgpt-api.ts:94](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L94) ___ @@ -78,7 +78,7 @@ ___ #### Defined in -[chatgpt-api.ts:93](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L93) +[chatgpt-api.ts:104](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L104) ___ @@ -92,7 +92,7 @@ ___ #### Defined in -[chatgpt-api.ts:113](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L113) +[chatgpt-api.ts:124](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L124) ___ @@ -106,7 +106,7 @@ ___ #### Defined in -[chatgpt-api.ts:103](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L103) +[chatgpt-api.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L114) ___ @@ -127,7 +127,7 @@ ___ #### Defined in -[chatgpt-api.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L48) +[chatgpt-api.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L48) ___ @@ -147,4 +147,4 @@ ___ #### Defined in -[chatgpt-api.ts:151](https://github.com/transitive-bullshit/chatgpt-api/blob/82a5232/src/chatgpt-api.ts#L151) +[chatgpt-api.ts:162](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L162) diff --git a/docs/readme.md b/docs/readme.md index e524a026f..892400e66 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -2,30 +2,42 @@ chatgpt / [Exports](modules.md) # ChatGPT API -> Node.js TS wrapper around [ChatGPT](https://openai.com/blog/chatgpt/). Uses headless Chrome until the official API is released. +> Node.js wrapper around [ChatGPT](https://openai.com/blog/chatgpt/). Uses headless Chrome until the official API is released. [![NPM](https://img.shields.io/npm/v/chatgpt.svg)](https://www.npmjs.com/package/chatgpt) [![Build Status](https://github.com/transitive-bullshit/chatgpt-api/actions/workflows/test.yml/badge.svg)](https://github.com/transitive-bullshit/chatgpt-api/actions/workflows/test.yml) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/transitive-bullshit/chatgpt-api/blob/main/license) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io) - [Intro](#intro) -- [Auth](#auth) +- [How it works](#how-it-works) +- [Install](#install) - [Usage](#usage) -- [Example](#example) - [Docs](#docs) -- [Todo](#todo) - [Related](#related) - [License](#license) ## Intro -This package is a Node.js TypeScript wrapper around [ChatGPT](https://openai.com/blog/chatgpt) by [OpenAI](https://openai.com). +This package is a Node.js wrapper around [ChatGPT](https://openai.com/blog/chatgpt) by [OpenAI](https://openai.com). TS batteries included. ✨ -You can use it to start experimenting with ChatGPT by integrating it into websites, chatbots, etc... +You can use it to start building projects powered by ChatGPT like chatbots, websites, etc... -## Auth +## How it works -It uses headless Chromium via [Playwright](https://playwright.dev) under the hood, so **you still need to have access to ChatGPT**, but it makes it much easier to access programatically. +We use headless Chromium via [Playwright](https://playwright.dev) to automate the webapp, so **you still need to have access to ChatGPT**. It just makes building API-like integrations much easier. -Chromium is opened in non-headless mode by default, which is important because the first time you run `ChatGPTAPI`.init, you'll need to log in manually. Chromium is launched with a persistent context, so you shouldn't need to keep re-logging in after the first time. +Chromium will be opened in non-headless mode by default, which is important because the first time you run `ChatGPTAPI.init()`, you'll need to log in manually. We launch Chromium with a persistent context, however, so you shouldn't need to keep re-logging in after the first time. When you log in the first time, _make sure that you also dismiss the welcome modal_. + +> **Note** +> We'll replace headless chrome with the official API once it's released. + +## Install + +```bash +npm install --save chatgpt +# or +yarn add chatgpt +# or +pnpm add chatgpt +``` ## Usage @@ -35,10 +47,10 @@ import { ChatGPTAPI } from 'chatgpt' async function example() { const api = new ChatGPTAPI() - // open chromium and wait until the user has logged in + // open chromium and wait until you've logged in await api.init({ auth: 'blocking' }) - // send a message and wait for a complete response, then parse it as markdown + // send a message and wait for the response const response = await api.sendMessage( 'Write a python version of bubble sort. Do not include example usage.' ) @@ -70,17 +82,18 @@ def bubble_sort(lst): return lst ``` -The default functionality is to parse ChatGPT responses as markdown using [html-to-md](https://github.com/stonehank/html-to-md). I've found the markdown parsing to work really well during my testing, but if you'd rather output plaintext, you can use: +By default, ChatGPT responses are parsed as markdown using [html-to-md](https://github.com/stonehank/html-to-md). I've found that this works really well during my testing, but if you'd rather output plaintext, you can use: ```ts const api = new ChatGPTAPI({ markdown: false }) ``` -## Example +A full [example](./src/example.ts) is included for testing purposes: -A full example is included for testing purposes: - -``` +```bash +# clone repo +# install node deps +# then run npx tsx src/example.ts ``` @@ -88,15 +101,10 @@ npx tsx src/example.ts See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods parameters. -## Todo - -- [ ] Add message and conversation IDs -- [ ] Add support for streaming responses -- [ ] Add basic unit tests - ## Related - Inspired by this [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross) +- [Python port](https://github.com/taranjeet/chatgpt-api) ## License diff --git a/readme.md b/readme.md index dfd024e0b..21156202e 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ You can use it to start building projects powered by ChatGPT like chatbots, webs We use headless Chromium via [Playwright](https://playwright.dev) to automate the webapp, so **you still need to have access to ChatGPT**. It just makes building API-like integrations much easier. -Chromium will be opened in non-headless mode by default, which is important because the first time you run `ChatGPTAPI.init()`, you'll need to log in manually. We launch Chromium with a persistent context, however, so you shouldn't need to keep re-logging in after the first time. +Chromium will be opened in non-headless mode by default, which is important because the first time you run `ChatGPTAPI.init()`, you'll need to log in manually. We launch Chromium with a persistent context, however, so you shouldn't need to keep re-logging in after the first time. When you log in the first time, _make sure that you also dismiss the welcome modal_. > **Note** > We'll replace headless chrome with the official API once it's released. diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index 2fb1d9410..0f81c41a9 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -58,14 +58,17 @@ export class ChatGPTAPI { // dismiss welcome modal do { const modalSelector = '[data-headlessui-state="open"]' - if (!(await this._page.isVisible(modalSelector, { timeout: 500 }))) { + + if (!(await this._page.$(modalSelector))) { break } - const modal = await this._page.locator(modalSelector) - if (modal) { - await modal.locator('button').last().click() - } else { + try { + await this._page.click(`${modalSelector} button:last-child`, { + timeout: 1000 + }) + } catch (err) { + // "next" button not found in welcome modal break } } while (true) @@ -77,7 +80,10 @@ export class ChatGPTAPI { break } - console.log('Please sign in to ChatGPT') + console.log( + 'Please sign in to ChatGPT using the Chromium browser window and dismiss the welcome modal...' + ) + await delay(1000) } while (true) } @@ -86,8 +92,13 @@ export class ChatGPTAPI { } async getIsSignedIn() { - const inputBox = await this._getInputBox() - return !!inputBox + try { + const inputBox = await this._getInputBox() + return !!inputBox + } catch (err) { + // can happen when navigating during login + return false + } } async getLastMessage(): Promise { @@ -154,8 +165,8 @@ export class ChatGPTAPI { const lastMessage = await this.getLastMessage() - await inputBox.click() - await inputBox.fill(message) + await inputBox.click({ force: true }) + await inputBox.fill(message, { force: true }) await inputBox.press('Enter') do { diff --git a/src/example.ts b/src/example.ts index 9343bed9a..49818741d 100644 --- a/src/example.ts +++ b/src/example.ts @@ -16,17 +16,21 @@ async function main() { // Wait until the user signs in via the chromium browser await oraPromise( new Promise(async (resolve, reject) => { - try { - await delay(1000) - const isSignedIn = await api.getIsSignedIn() - if (isSignedIn) { - return resolve() + do { + try { + await delay(1000) + + const isSignedIn = await api.getIsSignedIn() + + if (isSignedIn) { + return resolve() + } + } catch (err) { + return reject(err) } - } catch (err) { - return reject(err) - } + } while (true) }), - 'Please sign in to ChatGPT' + 'Please sign in to ChatGPT and dismiss the welcome modal' ) }