Commit 8cd86fc7 authored by Travis Fischer's avatar Travis Fischer Committed by GitHub

Merge pull request #9 from transitive-bullshit/feat/use-unofficial-rest-api

parents acf5261b ed92e9de
# ------------------------------------------------------------------------------
# This is an example .env file.
#
# All of these environment vars must be defined either in your environment or in
# a local .env file in order to run the demo for this project.
# ------------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# ChatGPT
# -----------------------------------------------------------------------------
# see the readme for how to find this
SESSION_TOKEN=
...@@ -10,53 +10,53 @@ ...@@ -10,53 +10,53 @@
### Methods ### Methods
- [close](ChatGPTAPI.md#close) - [ensureAuth](ChatGPTAPI.md#ensureauth)
- [getIsSignedIn](ChatGPTAPI.md#getissignedin) - [getIsAuthenticated](ChatGPTAPI.md#getisauthenticated)
- [getLastMessage](ChatGPTAPI.md#getlastmessage) - [refreshAccessToken](ChatGPTAPI.md#refreshaccesstoken)
- [getMessages](ChatGPTAPI.md#getmessages)
- [getPrompts](ChatGPTAPI.md#getprompts)
- [init](ChatGPTAPI.md#init)
- [sendMessage](ChatGPTAPI.md#sendmessage) - [sendMessage](ChatGPTAPI.md#sendmessage)
## Constructors ## Constructors
### constructor ### constructor
**new ChatGPTAPI**(`opts?`) **new ChatGPTAPI**(`opts`)
Creates a new client wrapper around the unofficial ChatGPT REST API.
#### Parameters #### Parameters
| Name | Type | Description | | Name | Type | Description |
| :------ | :------ | :------ | | :------ | :------ | :------ |
| `opts` | `Object` | - | | `opts` | `Object` | - |
| `opts.chatUrl?` | `string` | **`Default Value`** `'https://chat.openai.com/'` * | | `opts.apiBaseUrl?` | `string` | **`Default Value`** `'https://chat.openai.com/api'` * |
| `opts.headless?` | `boolean` | **`Default Value`** `false` * | | `opts.backendApiBaseUrl?` | `string` | **`Default Value`** `'https://chat.openai.com/backend-api'` * |
| `opts.markdown?` | `boolean` | **`Default Value`** `true` * | | `opts.markdown?` | `boolean` | **`Default Value`** `true` * |
| `opts.userDataDir?` | `string` | **`Default Value`** `'/tmp/chatgpt'` * | | `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'` * |
#### Defined in #### Defined in
[chatgpt-api.ts:20](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L20) [chatgpt-api.ts:31](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/chatgpt-api.ts#L31)
## Methods ## Methods
### close ### ensureAuth
**close**(): `Promise`<`void`\> **ensureAuth**(): `Promise`<`string`\>
#### Returns #### Returns
`Promise`<`void`\> `Promise`<`string`\>
#### Defined in #### Defined in
[chatgpt-api.ts:186](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L186) [chatgpt-api.ts:74](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/chatgpt-api.ts#L74)
___ ___
### getIsSignedIn ### getIsAuthenticated
**getIsSignedIn**(): `Promise`<`boolean`\> **getIsAuthenticated**(): `Promise`<`boolean`\>
#### Returns #### Returns
...@@ -64,13 +64,13 @@ ___ ...@@ -64,13 +64,13 @@ ___
#### Defined in #### Defined in
[chatgpt-api.ts:94](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L94) [chatgpt-api.ts:65](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/chatgpt-api.ts#L65)
___ ___
### getLastMessage ### refreshAccessToken
**getLastMessage**(): `Promise`<`string`\> **refreshAccessToken**(): `Promise`<`string`\>
#### Returns #### Returns
...@@ -78,68 +78,25 @@ ___ ...@@ -78,68 +78,25 @@ ___
#### Defined in #### Defined in
[chatgpt-api.ts:104](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L104) [chatgpt-api.ts:165](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/chatgpt-api.ts#L165)
___
### getMessages
**getMessages**(): `Promise`<`string`[]\>
#### Returns
`Promise`<`string`[]\>
#### Defined in
[chatgpt-api.ts:124](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L124)
___
### getPrompts
**getPrompts**(): `Promise`<`string`[]\>
#### Returns
`Promise`<`string`[]\>
#### Defined in
[chatgpt-api.ts:114](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L114)
___
### init
**init**(`opts?`): `Promise`<`Page`\>
#### Parameters
| Name | Type |
| :------ | :------ |
| `opts` | `Object` |
| `opts.auth?` | ``"blocking"`` \| ``"eager"`` |
#### Returns
`Promise`<`Page`\>
#### Defined in
[chatgpt-api.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L48)
___ ___
### sendMessage ### sendMessage
**sendMessage**(`message`): `Promise`<`string`\> **sendMessage**(`message`, `opts?`): `Promise`<`string`\>
Sends a message to ChatGPT, waits for the response to resolve, and returns
the response.
#### Parameters #### Parameters
| Name | Type | | Name | Type | Description |
| :------ | :------ | | :------ | :------ | :------ |
| `message` | `string` | | `message` | `string` | The plaintext message to send. |
| `opts` | `Object` | - |
| `opts.converstationId?` | `string` | - |
| `opts.onProgress?` | (`partialResponse`: `string`) => `void` | - |
#### Returns #### Returns
...@@ -147,4 +104,4 @@ ___ ...@@ -147,4 +104,4 @@ ___
#### Defined in #### Defined in
[chatgpt-api.ts:162](https://github.com/transitive-bullshit/chatgpt-api/blob/ddd9545/src/chatgpt-api.ts#L162) [chatgpt-api.ts:86](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/chatgpt-api.ts#L86)
...@@ -7,3 +7,386 @@ ...@@ -7,3 +7,386 @@
### Classes ### Classes
- [ChatGPTAPI](classes/ChatGPTAPI.md) - [ChatGPTAPI](classes/ChatGPTAPI.md)
### Type Aliases
- [AvailableModerationModels](modules.md#availablemoderationmodels)
- [ContentType](modules.md#contenttype)
- [ConversationJSONBody](modules.md#conversationjsonbody)
- [ConversationResponseEvent](modules.md#conversationresponseevent)
- [Message](modules.md#message)
- [MessageContent](modules.md#messagecontent)
- [MessageFeedbackJSONBody](modules.md#messagefeedbackjsonbody)
- [MessageFeedbackRating](modules.md#messagefeedbackrating)
- [MessageFeedbackResult](modules.md#messagefeedbackresult)
- [MessageFeedbackTags](modules.md#messagefeedbacktags)
- [MessageMetadata](modules.md#messagemetadata)
- [Model](modules.md#model)
- [ModelsResult](modules.md#modelsresult)
- [ModerationsJSONBody](modules.md#moderationsjsonbody)
- [ModerationsJSONResult](modules.md#moderationsjsonresult)
- [Prompt](modules.md#prompt)
- [PromptContent](modules.md#promptcontent)
- [Role](modules.md#role)
- [SessionResult](modules.md#sessionresult)
- [User](modules.md#user)
### Functions
- [markdownToText](modules.md#markdowntotext)
## Type Aliases
### AvailableModerationModels
Ƭ **AvailableModerationModels**: ``"text-moderation-playground"``
#### Defined in
[types.ts:104](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L104)
___
### ContentType
Ƭ **ContentType**: ``"text"``
#### Defined in
[types.ts:1](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L1)
___
### ConversationJSONBody
Ƭ **ConversationJSONBody**: `Object`
https://chat.openapi.com/backend-api/conversation
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `action` | `string` | The action to take |
| `conversation_id?` | `string` | The ID of the conversation |
| `messages` | [`Prompt`](modules.md#prompt)[] | Prompts to provide |
| `model` | `string` | The model to use |
| `parent_message_id` | `string` | The parent message ID |
#### Defined in
[types.ts:129](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L129)
___
### ConversationResponseEvent
Ƭ **ConversationResponseEvent**: `Object`
#### Type declaration
| Name | Type |
| :------ | :------ |
| `conversation_id?` | `string` |
| `error?` | `string` \| ``null`` |
| `message?` | [`Message`](modules.md#message) |
#### Defined in
[types.ts:246](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L246)
___
### Message
Ƭ **Message**: `Object`
#### Type declaration
| Name | Type |
| :------ | :------ |
| `content` | [`MessageContent`](modules.md#messagecontent) |
| `create_time` | `string` \| ``null`` |
| `end_turn` | ``null`` |
| `id` | `string` |
| `metadata` | [`MessageMetadata`](modules.md#messagemetadata) |
| `recipient` | `string` |
| `role` | `string` |
| `update_time` | `string` \| ``null`` |
| `user` | `string` \| ``null`` |
| `weight` | `number` |
#### Defined in
[types.ts:252](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L252)
___
### MessageContent
Ƭ **MessageContent**: `Object`
#### Type declaration
| Name | Type |
| :------ | :------ |
| `content_type` | `string` |
| `parts` | `string`[] |
#### Defined in
[types.ts:265](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L265)
___
### MessageFeedbackJSONBody
Ƭ **MessageFeedbackJSONBody**: `Object`
https://chat.openapi.com/backend-api/conversation/message_feedback
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `conversation_id` | `string` | The ID of the conversation |
| `message_id` | `string` | The message ID |
| `rating` | [`MessageFeedbackRating`](modules.md#messagefeedbackrating) | The rating |
| `tags?` | [`MessageFeedbackTags`](modules.md#messagefeedbacktags)[] | Tags to give the rating |
| `text?` | `string` | The text to include |
#### Defined in
[types.ts:188](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L188)
___
### MessageFeedbackRating
Ƭ **MessageFeedbackRating**: ``"thumbsUp"`` \| ``"thumbsDown"``
#### Defined in
[types.ts:244](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L244)
___
### MessageFeedbackResult
Ƭ **MessageFeedbackResult**: `Object`
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `conversation_id` | `string` | The ID of the conversation |
| `message_id` | `string` | The message ID |
| `rating` | [`MessageFeedbackRating`](modules.md#messagefeedbackrating) | The rating |
| `text?` | `string` | The text the server received, including tags |
| `user_id` | `string` | The ID of the user |
#### Defined in
[types.ts:217](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L217)
___
### MessageFeedbackTags
Ƭ **MessageFeedbackTags**: ``"harmful"`` \| ``"false"`` \| ``"not-helpful"``
#### Defined in
[types.ts:215](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L215)
___
### MessageMetadata
Ƭ **MessageMetadata**: `any`
#### Defined in
[types.ts:270](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L270)
___
### Model
Ƭ **Model**: `Object`
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `is_special` | `boolean` | Whether or not the model is special |
| `max_tokens` | `number` | Max tokens of the model |
| `slug` | `string` | Name of the model |
#### Defined in
[types.ts:72](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L72)
___
### ModelsResult
Ƭ **ModelsResult**: `Object`
https://chat.openapi.com/backend-api/models
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `models` | [`Model`](modules.md#model)[] | Array of models |
#### Defined in
[types.ts:65](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L65)
___
### ModerationsJSONBody
Ƭ **ModerationsJSONBody**: `Object`
https://chat.openapi.com/backend-api/moderations
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `input` | `string` | Input for the moderation decision |
| `model` | [`AvailableModerationModels`](modules.md#availablemoderationmodels) | The model to use in the decision |
#### Defined in
[types.ts:92](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L92)
___
### ModerationsJSONResult
Ƭ **ModerationsJSONResult**: `Object`
https://chat.openapi.com/backend-api/moderations
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `blocked` | `boolean` | Whether or not the input is blocked |
| `flagged` | `boolean` | Whether or not the input is flagged |
| `moderation_id` | `string` | The ID of the decision |
#### Defined in
[types.ts:109](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L109)
___
### Prompt
Ƭ **Prompt**: `Object`
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `content` | [`PromptContent`](modules.md#promptcontent) | The content of the prompt |
| `id` | `string` | The ID of the prompt |
| `role` | [`Role`](modules.md#role) | The role played in the prompt |
#### Defined in
[types.ts:156](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L156)
___
### PromptContent
Ƭ **PromptContent**: `Object`
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `content_type` | [`ContentType`](modules.md#contenttype) | The content type of the prompt |
| `parts` | `string`[] | The parts to the prompt |
#### Defined in
[types.ts:173](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L173)
___
### Role
Ƭ **Role**: ``"user"`` \| ``"assistant"``
#### Defined in
[types.ts:3](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L3)
___
### SessionResult
Ƭ **SessionResult**: `Object`
https://chat.openapi.com/api/auth/session
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `accessToken` | `string` | The access token |
| `expires` | `string` | ISO date of the expiration date of the access token |
| `user` | [`User`](modules.md#user) | Object of the current user |
#### Defined in
[types.ts:8](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L8)
___
### User
Ƭ **User**: `Object`
#### Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| `email` | `string` | Email of the user |
| `features` | `string`[] \| [] | Features the user is in |
| `groups` | `string`[] \| [] | Groups the user is in |
| `id` | `string` | ID of the user |
| `image` | `string` | Image of the user |
| `name` | `string` | Name of the user |
| `picture` | `string` | Picture of the user |
#### Defined in
[types.ts:25](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/types.ts#L25)
## Functions
### markdownToText
**markdownToText**(`markdown?`): `string`
#### Parameters
| Name | Type |
| :------ | :------ |
| `markdown?` | `string` |
#### Returns
`string`
#### Defined in
[utils.ts:4](https://github.com/transitive-bullshit/chatgpt-api/blob/c9cef79/src/utils.ts#L4)
chatgpt / [Exports](modules.md) chatgpt / [Exports](modules.md)
<p align="center">
<img alt="Example usage" src="/media/demo.gif">
</p>
# ChatGPT API <!-- omit in toc --> # ChatGPT API <!-- omit in toc -->
> Node.js wrapper around [ChatGPT](https://openai.com/blog/chatgpt/). Uses headless Chrome until the official API is released. > Node.js client for the unofficial [ChatGPT](https://openai.com/blog/chatgpt/) API.
[![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) [![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)
...@@ -11,7 +15,8 @@ chatgpt / [Exports](modules.md) ...@@ -11,7 +15,8 @@ chatgpt / [Exports](modules.md)
- [Install](#install) - [Install](#install)
- [Usage](#usage) - [Usage](#usage)
- [Docs](#docs) - [Docs](#docs)
- [Related](#related) - [Examples](#examples)
- [Credit](#credit)
- [License](#license) - [License](#license)
## Intro ## Intro
...@@ -22,12 +27,23 @@ You can use it to start building projects powered by ChatGPT like chatbots, webs ...@@ -22,12 +27,23 @@ You can use it to start building projects powered by ChatGPT like chatbots, webs
## How it works ## How it works
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. This package requires a valid session token from ChatGPT to access it's unofficial REST API.
To get a session token:
1. Go to https://chat.openai.com/chat and log in or sign up.
2. Open dev tools.
3. Open `Application` > `Cookies`.
![ChatGPT cookies](./media/session-token.png)
4. Copy the value for `__Secure-next-auth.session-token` and save it to your environment.
If you want to run the built-in demo, store this value as `SESSION_TOKEN` in a local `.env` file.
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**
> This package will switch to using the official API once it's released.
> **Note** > **Note**
> We'll replace headless chrome with the official API once it's released. > Prior to v1.0.0, this package used a headless browser via [Playwright](https://playwright.dev/) to automate the web UI. Here are the [docs for the initial browser version](https://github.com/transitive-bullshit/chatgpt-api/tree/v0.4.2).
## Install ## Install
...@@ -45,66 +61,62 @@ pnpm add chatgpt ...@@ -45,66 +61,62 @@ pnpm add chatgpt
import { ChatGPTAPI } from 'chatgpt' import { ChatGPTAPI } from 'chatgpt'
async function example() { async function example() {
const api = new ChatGPTAPI() const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
// open chromium and wait until you've logged in // ensure the API is properly authenticated (optional)
await api.init({ auth: 'blocking' }) await api.ensureAuth()
// send a message and wait for the response // send a message and wait for the response
const response = await api.sendMessage( const response = await api.sendMessage(
'Write a python version of bubble sort. Do not include example usage.' 'Write a python version of bubble sort. Do not include example usage.'
) )
// response is a markdown-formatted string
console.log(response) console.log(response)
} }
``` ```
Which outputs a similar reponse to this (as a markdown string, including the _```python_ code block prefix): By default, the response will be formatted as markdown. If you want to work with plaintext only, you can use:
```python
def bubble_sort(lst):
# Set the initial flag to True to start the loop
swapped = True
# Keep looping until there are no more swaps
while swapped:
# Set the flag to False initially
swapped = False
# Loop through the list
for i in range(len(lst) - 1):
# If the current element is greater than the next element,
# swap them and set the flag to True
if lst[i] > lst[i + 1]:
lst[i], lst[i + 1] = lst[i + 1], lst[i]
swapped = True
# Return the sorted list
return lst
```
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 ```ts
const api = new ChatGPTAPI({ markdown: false }) const api = new ChatGPTAPI({
sessionToken: process.env.SESSION_TOKEN,
markdown: false
})
``` ```
A full [example](./src/example.ts) is included for testing purposes: A full [example](./src/example.ts) is included for testing purposes:
```bash ```bash
# clone repo # 1. clone repo
# install node deps # 2. install node deps
# then run # 3. set `SESSION_TOKEN` in .env
# 4. run:
npx tsx src/example.ts npx tsx src/example.ts
``` ```
## Docs ## Docs
See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods parameters. See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods and parameters.
## Examples
All of these awesome projects use 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
- [Chrome Extension](https://github.com/gragland/chatgpt-everywhere) ([demo](https://twitter.com/gabe_ragland/status/1599466486422470656))
- [VSCode Extension](https://github.com/mpociot/chatgpt-vscode) ([demo](https://twitter.com/marcelpociot/status/1599180144551526400))
- [Go Telegram Bot](https://github.com/m1guelpf/chatgpt-telegram)
- [Github ProBot](https://github.com/oceanlvr/ChatGPTBot)
- [Lovelines.xyz](https://lovelines.xyz)
If you create a cool integration, feel free to open a PR and add it to the list.
## Related ## Credit
- Inspired by this [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross) - Huge thanks to [@RomanHotsiy](https://github.com/RomanHotsiy), [@ElijahPepe](https://github.com/ElijahPepe), [@wong2](https://github.com/wong2), and all the other contributors 💪
- [Python port](https://github.com/taranjeet/chatgpt-api) - The original browser version was inspired by this [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross)
## License ## License
......
# VHS documentation
#
# @see https://github.com/charmbracelet/vhs
#
# ```
# vhs < media/demo.tape
# ```
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set LoopOffset <float>% Set the starting frame offset for the GIF loop
# Set Theme <json|string> Set the theme of the terminal
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Backspace[@<time>] [number] Press the Backspace key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output
Output media/demo.gif
Hide
Require npx
#Set Shell bash
Set FontSize 17
Set Width 1200
Set Height 600
Set Padding 24
Set LoopOffset 75%
Type " "
Sleep 980ms
Backspace 1
Show
Type "npx tsx src/example.ts"
Sleep 500ms
Enter
Show
Sleep 8s
Sleep 8s
{ {
"name": "chatgpt", "name": "chatgpt",
"version": "0.4.2", "version": "0.4.2",
"description": "Node.js wrapper around ChatGPT. Uses headless Chrome until the official API is released.", "description": "Node.js client for the unofficial ChatGPT API.",
"author": "Travis Fischer <travis@transitivebullsh.it>", "author": "Travis Fischer <travis@transitivebullsh.it>",
"repository": "transitive-bullshit/chatgpt-api", "repository": "transitive-bullshit/chatgpt-api",
"license": "MIT", "license": "MIT",
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
".": { ".": {
"require": "./build/index.js", "require": "./build/index.js",
"import": "./build/index.mjs", "import": "./build/index.mjs",
"types": "./build/index.d.ts" "types": "./build/index.d.ts"
} }
}, },
"files": [ "files": [
...@@ -34,14 +34,19 @@ ...@@ -34,14 +34,19 @@
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check" "test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
}, },
"dependencies": { "dependencies": {
"html-to-md": "npm:@fisch0920/html-to-md@^0.8.1", "eventsource-parser": "^0.0.5",
"playwright": "^1.28.1" "expiry-map": "^2.0.0",
"node-fetch": "^3.3.0",
"remark": "^14.0.2",
"strip-markdown": "^5.0.0",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.0.0", "@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@types/uuid": "^9.0.0",
"del-cli": "^5.0.0", "del-cli": "^5.0.0",
"delay": "^5.0.0", "dotenv-safe": "^8.2.0",
"husky": "^8.0.2", "husky": "^8.0.2",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
......
...@@ -3,30 +3,40 @@ lockfileVersion: 5.4 ...@@ -3,30 +3,40 @@ lockfileVersion: 5.4
specifiers: specifiers:
'@trivago/prettier-plugin-sort-imports': ^4.0.0 '@trivago/prettier-plugin-sort-imports': ^4.0.0
'@types/node': ^18.11.9 '@types/node': ^18.11.9
'@types/uuid': ^9.0.0
del-cli: ^5.0.0 del-cli: ^5.0.0
delay: ^5.0.0 dotenv-safe: ^8.2.0
html-to-md: npm:@fisch0920/html-to-md@^0.8.1 eventsource-parser: ^0.0.5
expiry-map: ^2.0.0
husky: ^8.0.2 husky: ^8.0.2
lint-staged: ^13.0.3 lint-staged: ^13.0.3
node-fetch: ^3.3.0
npm-run-all: ^4.1.5 npm-run-all: ^4.1.5
ora: ^6.1.2 ora: ^6.1.2
playwright: ^1.28.1
prettier: ^2.8.0 prettier: ^2.8.0
remark: ^14.0.2
strip-markdown: ^5.0.0
tsup: ^6.5.0 tsup: ^6.5.0
tsx: ^3.12.1 tsx: ^3.12.1
typedoc: ^0.23.21 typedoc: ^0.23.21
typedoc-plugin-markdown: ^3.13.6 typedoc-plugin-markdown: ^3.13.6
typescript: ^4.9.3 typescript: ^4.9.3
uuid: ^9.0.0
dependencies: dependencies:
html-to-md: /@fisch0920/html-to-md/0.8.1 eventsource-parser: 0.0.5
playwright: 1.28.1 expiry-map: 2.0.0
node-fetch: 3.3.0
remark: 14.0.2
strip-markdown: 5.0.0
uuid: 9.0.0
devDependencies: devDependencies:
'@trivago/prettier-plugin-sort-imports': 4.0.0_prettier@2.8.0 '@trivago/prettier-plugin-sort-imports': 4.0.0_prettier@2.8.0
'@types/node': 18.11.10 '@types/node': 18.11.10
'@types/uuid': 9.0.0
del-cli: 5.0.0 del-cli: 5.0.0
delay: 5.0.0 dotenv-safe: 8.2.0
husky: 8.0.2 husky: 8.0.2
lint-staged: 13.0.4 lint-staged: 13.0.4
npm-run-all: 4.1.5 npm-run-all: 4.1.5
...@@ -323,10 +333,6 @@ packages: ...@@ -323,10 +333,6 @@ packages:
dev: true dev: true
optional: true optional: true
/@fisch0920/html-to-md/0.8.1:
resolution: {integrity: sha512-CEOoGuhwjNKLl1OQN4dAxLLK3zAYyQG8JDDbCOTRUc+9dDllvv1REfXjN1D0wQf+ZYEQr+EmfIvbKjkifX7x9w==}
dev: false
/@jridgewell/gen-mapping/0.1.1: /@jridgewell/gen-mapping/0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
...@@ -404,10 +410,26 @@ packages: ...@@ -404,10 +410,26 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:
'@types/ms': 0.7.31
dev: false
/@types/mdast/3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/minimist/1.2.2: /@types/minimist/1.2.2:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true dev: true
/@types/ms/0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false
/@types/node/18.11.10: /@types/node/18.11.10:
resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==} resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==}
dev: true dev: true
...@@ -416,6 +438,14 @@ packages: ...@@ -416,6 +438,14 @@ packages:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: true dev: true
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/@types/uuid/9.0.0:
resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==}
dev: true
/aggregate-error/3.1.0: /aggregate-error/3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'} engines: {node: '>=8'}
...@@ -495,6 +525,10 @@ packages: ...@@ -495,6 +525,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/bail/2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
dev: false
/balanced-match/1.0.2: /balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
...@@ -613,6 +647,10 @@ packages: ...@@ -613,6 +647,10 @@ packages:
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: true dev: true
/character-entities/2.0.2:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
dev: false
/chokidar/3.5.3: /chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
...@@ -743,6 +781,11 @@ packages: ...@@ -743,6 +781,11 @@ packages:
which: 2.0.2 which: 2.0.2
dev: true dev: true
/data-uri-to-buffer/4.0.0:
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
engines: {node: '>= 12'}
dev: false
/debug/4.3.4: /debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
...@@ -753,7 +796,6 @@ packages: ...@@ -753,7 +796,6 @@ packages:
optional: true optional: true
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
dev: true
/decamelize-keys/1.1.1: /decamelize-keys/1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
...@@ -773,6 +815,12 @@ packages: ...@@ -773,6 +815,12 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/decode-named-character-reference/1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
dependencies:
character-entities: 2.0.2
dev: false
/defaults/1.0.4: /defaults/1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
dependencies: dependencies:
...@@ -810,10 +858,15 @@ packages: ...@@ -810,10 +858,15 @@ packages:
slash: 4.0.0 slash: 4.0.0
dev: true dev: true
/delay/5.0.0: /dequal/2.0.3:
resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=10'} engines: {node: '>=6'}
dev: true dev: false
/diff/5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
dev: false
/dir-glob/3.0.1: /dir-glob/3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
...@@ -822,6 +875,17 @@ packages: ...@@ -822,6 +875,17 @@ packages:
path-type: 4.0.0 path-type: 4.0.0
dev: true dev: true
/dotenv-safe/8.2.0:
resolution: {integrity: sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==}
dependencies:
dotenv: 8.6.0
dev: true
/dotenv/8.6.0:
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
engines: {node: '>=10'}
dev: true
/eastasianwidth/0.2.0: /eastasianwidth/0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true dev: true
...@@ -1108,6 +1172,11 @@ packages: ...@@ -1108,6 +1172,11 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/eventsource-parser/0.0.5:
resolution: {integrity: sha512-BAq82bC3ZW9fPYYZlofXBOAfbpmDzXIOsj+GOehQwgTUYsQZ6HtHs6zuRtge7Ph8OhS6lNH1kJF8q9dj17RcmA==}
engines: {node: '>=12'}
dev: false
/execa/5.1.1: /execa/5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -1138,6 +1207,17 @@ packages: ...@@ -1138,6 +1207,17 @@ packages:
strip-final-newline: 3.0.0 strip-final-newline: 3.0.0
dev: true dev: true
/expiry-map/2.0.0:
resolution: {integrity: sha512-K1I5wJe2fiqjyUZf/xhxwTpaopw3F+19DsO7Oggl20+3SVTXDIevVRJav0aBMfposQdkl2E4+gnuOKd3j2X0sA==}
engines: {node: '>=8'}
dependencies:
map-age-cleaner: 0.2.0
dev: false
/extend/3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
/fast-glob/3.2.12: /fast-glob/3.2.12:
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
...@@ -1155,6 +1235,14 @@ packages: ...@@ -1155,6 +1235,14 @@ packages:
reusify: 1.0.4 reusify: 1.0.4
dev: true dev: true
/fetch-blob/3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: false
/fill-range/7.0.1: /fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
...@@ -1170,6 +1258,13 @@ packages: ...@@ -1170,6 +1258,13 @@ packages:
path-exists: 4.0.0 path-exists: 4.0.0
dev: true dev: true
/formdata-polyfill/4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: false
/fs.realpath/1.0.0: /fs.realpath/1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
...@@ -1434,6 +1529,11 @@ packages: ...@@ -1434,6 +1529,11 @@ packages:
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
dev: true dev: true
/is-buffer/2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
dev: false
/is-callable/1.2.7: /is-callable/1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -1511,6 +1611,11 @@ packages: ...@@ -1511,6 +1611,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/is-plain-obj/4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
dev: false
/is-regex/1.1.4: /is-regex/1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -1606,6 +1711,11 @@ packages: ...@@ -1606,6 +1711,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/kleur/4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
dev: false
/lilconfig/2.0.6: /lilconfig/2.0.6:
resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -1705,6 +1815,10 @@ packages: ...@@ -1705,6 +1815,10 @@ packages:
wrap-ansi: 6.2.0 wrap-ansi: 6.2.0
dev: true dev: true
/longest-streak/3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
dev: false
/lru-cache/6.0.0: /lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -1716,6 +1830,13 @@ packages: ...@@ -1716,6 +1830,13 @@ packages:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
dev: true dev: true
/map-age-cleaner/0.2.0:
resolution: {integrity: sha512-AvxTC6id0fzSf6OyNBTp1syyCuKO7nOJvHgYlhT0Qkkjvk40zZo+av3ayVgXlxnF/DxEzEfY9mMdd7FHsd+wKQ==}
engines: {node: '>=7.6'}
dependencies:
p-defer: 1.0.0
dev: false
/map-obj/1.0.1: /map-obj/1.0.1:
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
...@@ -1732,6 +1853,41 @@ packages: ...@@ -1732,6 +1853,41 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/mdast-util-from-markdown/1.2.0:
resolution: {integrity: sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==}
dependencies:
'@types/mdast': 3.0.10
'@types/unist': 2.0.6
decode-named-character-reference: 1.0.2
mdast-util-to-string: 3.1.0
micromark: 3.1.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-decode-string: 1.0.2
micromark-util-normalize-identifier: 1.0.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
unist-util-stringify-position: 3.0.2
uvu: 0.5.6
transitivePeerDependencies:
- supports-color
dev: false
/mdast-util-to-markdown/1.3.0:
resolution: {integrity: sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==}
dependencies:
'@types/mdast': 3.0.10
'@types/unist': 2.0.6
longest-streak: 3.1.0
mdast-util-to-string: 3.1.0
micromark-util-decode-string: 1.0.2
unist-util-visit: 4.1.1
zwitch: 2.0.4
dev: false
/mdast-util-to-string/3.1.0:
resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
dev: false
/memorystream/0.3.1: /memorystream/0.3.1:
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
...@@ -1764,6 +1920,182 @@ packages: ...@@ -1764,6 +1920,182 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true dev: true
/micromark-core-commonmark/1.0.6:
resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
dependencies:
decode-named-character-reference: 1.0.2
micromark-factory-destination: 1.0.0
micromark-factory-label: 1.0.2
micromark-factory-space: 1.0.0
micromark-factory-title: 1.0.2
micromark-factory-whitespace: 1.0.0
micromark-util-character: 1.1.0
micromark-util-chunked: 1.0.0
micromark-util-classify-character: 1.0.0
micromark-util-html-tag-name: 1.1.0
micromark-util-normalize-identifier: 1.0.0
micromark-util-resolve-all: 1.0.0
micromark-util-subtokenize: 1.0.2
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-destination/1.0.0:
resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-factory-label/1.0.2:
resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-space/1.0.0:
resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-types: 1.0.2
dev: false
/micromark-factory-title/1.0.2:
resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==}
dependencies:
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-whitespace/1.0.0:
resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==}
dependencies:
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-character/1.1.0:
resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==}
dependencies:
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-chunked/1.0.0:
resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-classify-character/1.0.0:
resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-combine-extensions/1.0.0:
resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==}
dependencies:
micromark-util-chunked: 1.0.0
micromark-util-types: 1.0.2
dev: false
/micromark-util-decode-numeric-character-reference/1.0.0:
resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-decode-string/1.0.2:
resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==}
dependencies:
decode-named-character-reference: 1.0.2
micromark-util-character: 1.1.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-encode/1.0.1:
resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==}
dev: false
/micromark-util-html-tag-name/1.1.0:
resolution: {integrity: sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==}
dev: false
/micromark-util-normalize-identifier/1.0.0:
resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-resolve-all/1.0.0:
resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==}
dependencies:
micromark-util-types: 1.0.2
dev: false
/micromark-util-sanitize-uri/1.1.0:
resolution: {integrity: sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-encode: 1.0.1
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-subtokenize/1.0.2:
resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==}
dependencies:
micromark-util-chunked: 1.0.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-util-symbol/1.0.1:
resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==}
dev: false
/micromark-util-types/1.0.2:
resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==}
dev: false
/micromark/3.1.0:
resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==}
dependencies:
'@types/debug': 4.1.7
debug: 4.3.4
decode-named-character-reference: 1.0.2
micromark-core-commonmark: 1.0.6
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-chunked: 1.0.0
micromark-util-combine-extensions: 1.0.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-encode: 1.0.1
micromark-util-normalize-identifier: 1.0.0
micromark-util-resolve-all: 1.0.0
micromark-util-sanitize-uri: 1.1.0
micromark-util-subtokenize: 1.0.2
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
transitivePeerDependencies:
- supports-color
dev: false
/micromatch/4.0.5: /micromatch/4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
...@@ -1813,9 +2145,13 @@ packages: ...@@ -1813,9 +2145,13 @@ packages:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
dev: true dev: true
/mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
dev: false
/ms/2.1.2: /ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/mz/2.7.0: /mz/2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
...@@ -1833,6 +2169,20 @@ packages: ...@@ -1833,6 +2169,20 @@ packages:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: true dev: true
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch/3.3.0:
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: false
/node-releases/2.0.6: /node-releases/2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
dev: true dev: true
...@@ -1950,6 +2300,11 @@ packages: ...@@ -1950,6 +2300,11 @@ packages:
wcwidth: 1.0.1 wcwidth: 1.0.1
dev: true dev: true
/p-defer/1.0.0:
resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==}
engines: {node: '>=4'}
dev: false
/p-limit/3.1.0: /p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -2068,21 +2423,6 @@ packages: ...@@ -2068,21 +2423,6 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dev: true dev: true
/playwright-core/1.28.1:
resolution: {integrity: sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==}
engines: {node: '>=14'}
hasBin: true
dev: false
/playwright/1.28.1:
resolution: {integrity: sha512-92Sz6XBlfHlb9tK5UCDzIFAuIkHHpemA9zwUaqvo+w7sFMSmVMGmvKcbptof/eJObq63PGnMhM75x7qxhTR78Q==}
engines: {node: '>=14'}
hasBin: true
requiresBuild: true
dependencies:
playwright-core: 1.28.1
dev: false
/postcss-load-config/3.1.4: /postcss-load-config/3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
...@@ -2180,6 +2520,35 @@ packages: ...@@ -2180,6 +2520,35 @@ packages:
functions-have-names: 1.2.3 functions-have-names: 1.2.3
dev: true dev: true
/remark-parse/10.0.1:
resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==}
dependencies:
'@types/mdast': 3.0.10
mdast-util-from-markdown: 1.2.0
unified: 10.1.2
transitivePeerDependencies:
- supports-color
dev: false
/remark-stringify/10.0.2:
resolution: {integrity: sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==}
dependencies:
'@types/mdast': 3.0.10
mdast-util-to-markdown: 1.3.0
unified: 10.1.2
dev: false
/remark/14.0.2:
resolution: {integrity: sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==}
dependencies:
'@types/mdast': 3.0.10
remark-parse: 10.0.1
remark-stringify: 10.0.2
unified: 10.1.2
transitivePeerDependencies:
- supports-color
dev: false
/resolve-from/5.0.0: /resolve-from/5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'} engines: {node: '>=8'}
...@@ -2246,6 +2615,13 @@ packages: ...@@ -2246,6 +2615,13 @@ packages:
tslib: 2.4.1 tslib: 2.4.1
dev: true dev: true
/sade/1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
dependencies:
mri: 1.2.0
dev: false
/safe-buffer/5.2.1: /safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true dev: true
...@@ -2496,6 +2872,14 @@ packages: ...@@ -2496,6 +2872,14 @@ packages:
min-indent: 1.0.1 min-indent: 1.0.1
dev: true dev: true
/strip-markdown/5.0.0:
resolution: {integrity: sha512-PXSts6Ta9A/TwGxVVSRlQs1ukJTAwwtbip2OheJEjPyfykaQ4sJSTnQWjLTI2vYWNts/R/91/csagp15W8n9gA==}
dependencies:
'@types/mdast': 3.0.10
'@types/unist': 2.0.6
unified: 10.1.2
dev: false
/sucrase/3.29.0: /sucrase/3.29.0:
resolution: {integrity: sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==} resolution: {integrity: sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==}
engines: {node: '>=8'} engines: {node: '>=8'}
...@@ -2566,6 +2950,10 @@ packages: ...@@ -2566,6 +2950,10 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/trough/2.1.0:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: false
/ts-interface-checker/0.1.13: /ts-interface-checker/0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true dev: true
...@@ -2677,6 +3065,43 @@ packages: ...@@ -2677,6 +3065,43 @@ packages:
which-boxed-primitive: 1.0.2 which-boxed-primitive: 1.0.2
dev: true dev: true
/unified/10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
'@types/unist': 2.0.6
bail: 2.0.2
extend: 3.0.2
is-buffer: 2.0.5
is-plain-obj: 4.1.0
trough: 2.1.0
vfile: 5.3.6
dev: false
/unist-util-is/5.1.1:
resolution: {integrity: sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==}
dev: false
/unist-util-stringify-position/3.0.2:
resolution: {integrity: sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/5.1.1:
resolution: {integrity: sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.1.1
dev: false
/unist-util-visit/4.1.1:
resolution: {integrity: sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.1.1
unist-util-visit-parents: 5.1.1
dev: false
/update-browserslist-db/1.0.10_browserslist@4.21.4: /update-browserslist-db/1.0.10_browserslist@4.21.4:
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true hasBin: true
...@@ -2692,6 +3117,22 @@ packages: ...@@ -2692,6 +3117,22 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true dev: true
/uuid/9.0.0:
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
hasBin: true
dev: false
/uvu/0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}
hasBin: true
dependencies:
dequal: 2.0.3
diff: 5.1.0
kleur: 4.1.5
sade: 1.8.1
dev: false
/validate-npm-package-license/3.0.4: /validate-npm-package-license/3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies: dependencies:
...@@ -2699,6 +3140,22 @@ packages: ...@@ -2699,6 +3140,22 @@ packages:
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
dev: true dev: true
/vfile-message/3.1.3:
resolution: {integrity: sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.2
dev: false
/vfile/5.3.6:
resolution: {integrity: sha512-ADBsmerdGBs2WYckrLBEmuETSPyTD4TuLxTrw0DvjirxW1ra4ZwkbzG8ndsv3Q57smvHxo677MHaQrY9yxH8cA==}
dependencies:
'@types/unist': 2.0.6
is-buffer: 2.0.5
unist-util-stringify-position: 3.0.2
vfile-message: 3.1.3
dev: false
/vscode-oniguruma/1.7.0: /vscode-oniguruma/1.7.0:
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
dev: true dev: true
...@@ -2713,6 +3170,11 @@ packages: ...@@ -2713,6 +3170,11 @@ packages:
defaults: 1.0.4 defaults: 1.0.4
dev: true dev: true
/web-streams-polyfill/3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
dev: false
/webidl-conversions/4.0.2: /webidl-conversions/4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true dev: true
...@@ -2799,3 +3261,7 @@ packages: ...@@ -2799,3 +3261,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zwitch/2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
<p align="center">
<img alt="Example usage" src="/media/demo.gif">
</p>
# ChatGPT API <!-- omit in toc --> # ChatGPT API <!-- omit in toc -->
> Node.js wrapper around [ChatGPT](https://openai.com/blog/chatgpt/). Uses headless Chrome until the official API is released. > Node.js client for the unofficial [ChatGPT](https://openai.com/blog/chatgpt/) API.
[![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) [![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)
...@@ -10,7 +14,7 @@ ...@@ -10,7 +14,7 @@
- [Usage](#usage) - [Usage](#usage)
- [Docs](#docs) - [Docs](#docs)
- [Examples](#examples) - [Examples](#examples)
- [Related](#related) - [Credit](#credit)
- [License](#license) - [License](#license)
## Intro ## Intro
...@@ -21,17 +25,23 @@ You can use it to start building projects powered by ChatGPT like chatbots, webs ...@@ -21,17 +25,23 @@ You can use it to start building projects powered by ChatGPT like chatbots, webs
## How it works ## How it works
It uses 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. This package requires a valid session token from ChatGPT to access it's unofficial REST API.
To get a session token:
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 session, however, so you shouldn't need to keep re-logging in after the first time. 1. Go to https://chat.openai.com/chat and log in or sign up.
2. Open dev tools.
3. Open `Application` > `Cookies`.
![ChatGPT cookies](./media/session-token.png)
4. Copy the value for `__Secure-next-auth.session-token` and save it to your environment.
When you log in the first time, we recommend dismissing the welcome modal so you can watch the progress. This isn't strictly necessary, but it helps to understand what's going on. If you want to run the built-in demo, store this value as `SESSION_TOKEN` in a local `.env` file.
- [Demo video](https://www.loom.com/share/0c44525b07354d679f30c45d8eec6271) showing how the initial auth flow works (29 seconds) > **Note**
- [Demo video](https://www.loom.com/share/98e712dbddf843289e2b6615095bbdd7) showing how it works if you're already authed (13 seconds) > This package will switch to using the official API once it's released.
> **Note** > **Note**
> We'll replace headless chrome with the official API once it's released. > Prior to v1.0.0, this package used a headless browser via [Playwright](https://playwright.dev/) to automate the web UI. Here are the [docs for the initial browser version](https://github.com/transitive-bullshit/chatgpt-api/tree/v0.4.2).
## Install ## Install
...@@ -49,10 +59,10 @@ pnpm add chatgpt ...@@ -49,10 +59,10 @@ pnpm add chatgpt
import { ChatGPTAPI } from 'chatgpt' import { ChatGPTAPI } from 'chatgpt'
async function example() { async function example() {
const api = new ChatGPTAPI() const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
// open chromium and wait until you've logged in // ensure the API is properly authenticated (optional)
await api.init({ auth: 'blocking' }) await api.ensureAuth()
// send a message and wait for the response // send a message and wait for the response
const response = await api.sendMessage( const response = await api.sendMessage(
...@@ -64,18 +74,22 @@ async function example() { ...@@ -64,18 +74,22 @@ async function example() {
} }
``` ```
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: By default, the response will be formatted as markdown. If you want to work with plaintext only, you can use:
```ts ```ts
const api = new ChatGPTAPI({ markdown: false }) const api = new ChatGPTAPI({
sessionToken: process.env.SESSION_TOKEN,
markdown: false
})
``` ```
A full [example](./src/example.ts) is included for testing purposes: A full [example](./src/example.ts) is included for testing purposes:
```bash ```bash
# clone repo # 1. clone repo
# install node deps # 2. install node deps
# then run # 3. set `SESSION_TOKEN` in .env
# 4. run:
npx tsx src/example.ts npx tsx src/example.ts
``` ```
...@@ -97,10 +111,10 @@ All of these awesome projects use the `chatgpt` package. 🤯 ...@@ -97,10 +111,10 @@ All of these awesome projects use the `chatgpt` package. 🤯
If you create a cool integration, feel free to open a PR and add it to the list. If you create a cool integration, feel free to open a PR and add it to the list.
## Related ## Credit
- Inspired by this [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross) - Huge thanks to [@RomanHotsiy](https://github.com/RomanHotsiy), [@ElijahPepe](https://github.com/ElijahPepe), [@wong2](https://github.com/wong2), and all the other contributors 💪
- [Python port](https://github.com/taranjeet/chatgpt-api) - The original browser version was inspired by this [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross)
## License ## License
......
import delay from 'delay' import { createParser } from 'eventsource-parser'
import html2md from 'html-to-md' import ExpiryMap from 'expiry-map'
import { type ChromiumBrowserContext, type Page, chromium } from 'playwright' import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
import * as types from './types'
import { markdownToText } from './utils'
const KEY_ACCESS_TOKEN = 'accessToken'
const USER_AGENT =
'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'
export class ChatGPTAPI { export class ChatGPTAPI {
protected _userDataDir: string protected _sessionToken: string
protected _headless: boolean
protected _markdown: boolean protected _markdown: boolean
protected _chatUrl: string protected _apiBaseUrl: string
protected _backendApiBaseUrl: string
protected _userAgent: string
protected _browser: ChromiumBrowserContext // stores access tokens for up to 10 seconds before needing to refresh
protected _page: Page protected _accessTokenCache = new ExpiryMap<string, string>(10 * 1000)
/** /**
* @param opts.userDataDir — Path to a directory for storing persistent chromium session data * Creates a new client wrapper around the unofficial ChatGPT REST API.
* @param opts.chatUrl — OpenAI chat URL *
* @param opts.headless - Whether or not to use headless mode * @param opts.sessionToken = **Required** OpenAI session token which can be found in a valid session's cookies (see readme for instructions)
* @param opts.markdown — Whether or not to parse chat messages as markdown * @param apiBaseUrl - Optional override; the base URL for ChatGPT webapp's API (`/api`)
* @param backendApiBaseUrl - Optional override; the base URL for the ChatGPT backend API (`/backend-api`)
* @param userAgent - Optional override; the `user-agent` header to use with ChatGPT requests
*/ */
constructor( constructor(opts: {
opts: { sessionToken: string
/** @defaultValue `'/tmp/chatgpt'` **/
userDataDir?: string
/** @defaultValue `'https://chat.openai.com/'` **/ /** @defaultValue `true` **/
chatUrl?: string markdown?: boolean
/** @defaultValue `false` **/ /** @defaultValue `'https://chat.openai.com/api'` **/
headless?: boolean apiBaseUrl?: string
/** @defaultValue `true` **/ /** @defaultValue `'https://chat.openai.com/backend-api'` **/
markdown?: boolean backendApiBaseUrl?: string
} = {}
) { /** @defaultValue `'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'` **/
userAgent?: string
}) {
const { const {
userDataDir = '/tmp/chatgpt', sessionToken,
chatUrl = 'https://chat.openai.com/', markdown = true,
headless = false, apiBaseUrl = 'https://chat.openai.com/api',
markdown = true backendApiBaseUrl = 'https://chat.openai.com/backend-api',
userAgent = USER_AGENT
} = opts } = opts
this._userDataDir = userDataDir this._sessionToken = sessionToken
this._headless = !!headless
this._chatUrl = chatUrl
this._markdown = !!markdown this._markdown = !!markdown
} this._apiBaseUrl = apiBaseUrl
this._backendApiBaseUrl = backendApiBaseUrl
async init(opts: { auth?: 'blocking' | 'eager' } = {}) { this._userAgent = userAgent
const { auth = 'eager' } = opts
if (this._browser) { if (!this._sessionToken) {
await this.close() throw new Error('ChatGPT invalid session token')
} }
this._browser = await chromium.launchPersistentContext(this._userDataDir, {
headless: this._headless
})
this._page = await this._browser.newPage()
await this._page.goto(this._chatUrl)
// 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`, {
timeout: 1000
})
} catch (err) {
// "next" button not found in welcome modal
break
}
} while (true)
if (auth === 'blocking') {
do {
const isSignedIn = await this.getIsSignedIn()
if (isSignedIn) {
break
}
console.log(
'Please sign in to ChatGPT using the Chromium browser window and dismiss the welcome modal...'
)
await delay(1000)
} while (true)
}
return this._page
} }
async getIsSignedIn() { async getIsAuthenticated() {
try { try {
const inputBox = await this._getInputBox() void (await this.refreshAccessToken())
return !!inputBox return true
} catch (err) { } catch (err) {
// can happen when navigating during login
return false return false
} }
} }
async getLastMessage(): Promise<string | null> { async ensureAuth() {
const messages = await this.getMessages() return await this.refreshAccessToken()
if (messages) {
return messages[messages.length - 1]
} else {
return null
}
} }
async getPrompts(): Promise<string[]> { /**
// Get all prompts * Sends a message to ChatGPT, waits for the response to resolve, and returns
const messages = await this._page.$$( * the response.
'[class*="ConversationItem__Message"]:has([class*="ConversationItem__ActionButtons"]):has([class*="ConversationItem__Role"] [class*="Avatar__Wrapper"])' *
) * @param message - The plaintext message to send.
* @param opts.conversationId - Optional ID of the previous message in a conversation
* @param opts.onProgress - Optional listener which will be called every time the partial response is updated
*/
async sendMessage(
message: string,
opts: {
converstationId?: string
onProgress?: (partialResponse: string) => void
} = {}
): Promise<string> {
const { converstationId = uuidv4(), onProgress } = opts
const accessToken = await this.refreshAccessToken()
const body: types.ConversationJSONBody = {
action: 'next',
messages: [
{
id: uuidv4(),
role: 'user',
content: {
content_type: 'text',
parts: [message]
}
}
],
model: 'text-davinci-002-render',
parent_message_id: converstationId
}
// prompts are always plaintext const url = `${this._backendApiBaseUrl}/conversation`
return Promise.all(messages.map((a) => a.innerText()))
// TODO: What's the best way to differentiate btwn wanting just the response text
// versus wanting the full response message, so you can extract the ID and other
// metadata?
// let fullResponse: types.Message = null
let response = ''
return new Promise((resolve, reject) => {
this._fetchSSE(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'user-agent': this._userAgent
},
body: JSON.stringify(body),
onMessage: (data: string) => {
if (data === '[DONE]') {
return resolve(response)
}
try {
const parsedData: types.ConversationResponseEvent = JSON.parse(data)
const message = parsedData.message
// console.log('event', JSON.stringify(parsedData, null, 2))
if (message) {
let text = message?.content?.parts?.[0]
if (text) {
if (!this._markdown) {
text = markdownToText(text)
}
response = text
// fullResponse = message
if (onProgress) {
onProgress(text)
}
}
}
} catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
}
}).catch(reject)
})
} }
async getMessages(): Promise<string[]> { async refreshAccessToken(): Promise<string> {
// Get all complete messages const cachedAccessToken = this._accessTokenCache.get(KEY_ACCESS_TOKEN)
// (in-progress messages that are being streamed back don't contain action buttons) if (cachedAccessToken) {
const messages = await this._page.$$( return cachedAccessToken
'[class*="ConversationItem__Message"]:has([class*="ConversationItem__ActionButtons"]):not(:has([class*="ConversationItem__Role"] [class*="Avatar__Wrapper"]))'
)
if (this._markdown) {
const htmlMessages = await Promise.all(messages.map((a) => a.innerHTML()))
const markdownMessages = htmlMessages.map((messageHtml) => {
// parse markdown from message HTML
messageHtml = messageHtml.replace('Copy code</button>', '</button>')
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.innerText())
)
return plaintextMessages
} }
}
async sendMessage(message: string): Promise<string> {
const inputBox = await this._getInputBox()
if (!inputBox) throw new Error('not signed in')
const lastMessage = await this.getLastMessage()
await inputBox.click({ force: true }) try {
await inputBox.fill(message, { force: true }) const res = await fetch('https://chat.openai.com/api/auth/session', {
await inputBox.press('Enter') headers: {
cookie: `__Secure-next-auth.session-token=${this._sessionToken}`,
'user-agent': this._userAgent
}
}).then((r) => r.json() as any as types.SessionResult)
do { const accessToken = res?.accessToken
await delay(1000)
// TODO: this logic needs some work because we can have repeat messages... if (!accessToken) {
const newLastMessage = await this.getLastMessage() console.warn('no auth token', res)
if ( throw new Error('Unauthorized')
newLastMessage &&
lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase()
) {
return newLastMessage
} }
} while (true)
}
async resetThread() { this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken)
const resetButton = await this._page.$('nav > a:nth-child(1)') return accessToken
if (!resetButton) throw new Error('not signed in') } catch (err: any) {
throw new Error(`ChatGPT failed to refresh auth token: ${err.toString()}`)
await resetButton.click() }
} }
async close() { protected async _fetchSSE(
await this._browser.close() url: string,
this._page = null options: Parameters<typeof fetch>[1] & { onMessage: (data: string) => void }
this._browser = null ) {
} const { onMessage, ...fetchOptions } = options
const resp = await fetch(url, fetchOptions)
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
protected async _getInputBox(): Promise<any> { resp.body.on('readable', () => {
return this._page.$( let chunk: string | Buffer
'div[class*="PromptTextarea__TextareaWrapper"] textarea' while (null !== (chunk = resp.body.read())) {
) parser.feed(chunk.toString())
}
})
} }
} }
import delay from 'delay' import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora' import { oraPromise } from 'ora'
import { ChatGPTAPI } from './chatgpt-api' import { ChatGPTAPI } from './chatgpt-api'
dotenv.config()
/** /**
* Example CLI for testing functionality. * Example CLI for testing functionality.
*/ */
async function main() { async function main() {
const api = new ChatGPTAPI() const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
await api.init() await api.ensureAuth()
const isSignedIn = await api.getIsSignedIn()
if (!isSignedIn) {
// Wait until the user signs in via the chromium browser
await oraPromise(
new Promise<void>(async (resolve, reject) => {
do {
try {
await delay(1000)
const isSignedIn = await api.getIsSignedIn()
if (isSignedIn) { const prompt =
return resolve()
}
} catch (err) {
return reject(err)
}
} while (true)
}),
'Please sign in to ChatGPT and dismiss the welcome modal'
)
}
const response = await api.sendMessage(
// 'Write a TypeScript function for conway sort.'
'Write a python version of bubble sort. Do not include example usage.' 'Write a python version of bubble sort. Do not include example usage.'
)
// const prompts = await api.getPrompts()
// const messages = await api.getMessages()
// console.log('prompts', prompts)
// console.log('messages', messages)
// Wait forever; useful for debugging chromium sessions
// await new Promise(() => {})
await api.close() const response = await oraPromise(api.sendMessage(prompt), {
text: prompt
})
return response return response
} }
main().then((res) => { main()
console.log(res) .then((res) => {
}) console.log(res)
})
.catch((err) => {
console.error(err)
process.exit(1)
})
export * from './chatgpt-api' export * from './chatgpt-api'
export * from './chatgpt-api-types' export * from './types'
export * from './utils'
export type ContentType = 'text'
export type Role = 'user' | 'assistant'
/** /**
* https://chat.openapi.com/api/auth/session * https://chat.openapi.com/api/auth/session
*/ */
...@@ -5,7 +9,7 @@ export type SessionResult = { ...@@ -5,7 +9,7 @@ export type SessionResult = {
/** /**
* Object of the current user * Object of the current user
*/ */
user: APIUser user: User
/** /**
* ISO date of the expiration date of the access token * ISO date of the expiration date of the access token
...@@ -18,7 +22,7 @@ export type SessionResult = { ...@@ -18,7 +22,7 @@ export type SessionResult = {
accessToken: string accessToken: string
} }
export type APIUser = { export type User = {
/** /**
* ID of the user * ID of the user
*/ */
...@@ -62,10 +66,10 @@ export type ModelsResult = { ...@@ -62,10 +66,10 @@ export type ModelsResult = {
/** /**
* Array of models * Array of models
*/ */
models: APIModel[] models: Model[]
} }
export type APIModel = { export type Model = {
/** /**
* Name of the model * Name of the model
*/ */
...@@ -136,7 +140,7 @@ export type ConversationJSONBody = { ...@@ -136,7 +140,7 @@ export type ConversationJSONBody = {
/** /**
* Prompts to provide * Prompts to provide
*/ */
messages: APIPrompt[] messages: Prompt[]
/** /**
* The model to use * The model to use
...@@ -149,11 +153,11 @@ export type ConversationJSONBody = { ...@@ -149,11 +153,11 @@ export type ConversationJSONBody = {
parent_message_id: string parent_message_id: string
} }
export type APIPrompt = { export type Prompt = {
/** /**
* The content of the prompt * The content of the prompt
*/ */
content: APIPromptContent content: PromptContent
/** /**
* The ID of the prompt * The ID of the prompt
...@@ -163,14 +167,14 @@ export type APIPrompt = { ...@@ -163,14 +167,14 @@ export type APIPrompt = {
/** /**
* The role played in the prompt * The role played in the prompt
*/ */
role: APIPromptRole role: Role
} }
export type APIPromptContent = { export type PromptContent = {
/** /**
* The content type of the prompt * The content type of the prompt
*/ */
content_type: APIPromptContentType content_type: ContentType
/** /**
* The parts to the prompt * The parts to the prompt
...@@ -178,10 +182,6 @@ export type APIPromptContent = { ...@@ -178,10 +182,6 @@ export type APIPromptContent = {
parts: string[] parts: string[]
} }
export type APIPromptContentType = 'text'
export type APIPromptRole = 'user'
/** /**
* https://chat.openapi.com/backend-api/conversation/message_feedback * https://chat.openapi.com/backend-api/conversation/message_feedback
*/ */
...@@ -199,12 +199,12 @@ export type MessageFeedbackJSONBody = { ...@@ -199,12 +199,12 @@ export type MessageFeedbackJSONBody = {
/** /**
* The rating * The rating
*/ */
rating: APIMessageFeedbackRating rating: MessageFeedbackRating
/** /**
* Tags to give the rating * Tags to give the rating
*/ */
tags?: APIMessageFeedbackTags[] tags?: MessageFeedbackTags[]
/** /**
* The text to include * The text to include
...@@ -212,7 +212,7 @@ export type MessageFeedbackJSONBody = { ...@@ -212,7 +212,7 @@ export type MessageFeedbackJSONBody = {
text?: string text?: string
} }
export type APIMessageFeedbackTags = 'harmful' | 'false' | 'not-helpful' export type MessageFeedbackTags = 'harmful' | 'false' | 'not-helpful'
export type MessageFeedbackResult = { export type MessageFeedbackResult = {
/** /**
...@@ -233,7 +233,7 @@ export type MessageFeedbackResult = { ...@@ -233,7 +233,7 @@ export type MessageFeedbackResult = {
/** /**
* The rating * The rating
*/ */
rating: APIMessageFeedbackRating rating: MessageFeedbackRating
/** /**
* The text the server received, including tags * The text the server received, including tags
...@@ -241,4 +241,30 @@ export type MessageFeedbackResult = { ...@@ -241,4 +241,30 @@ export type MessageFeedbackResult = {
text?: string text?: string
} }
export type APIMessageFeedbackRating = 'thumbsUp' | 'thumbsDown' export type MessageFeedbackRating = 'thumbsUp' | 'thumbsDown'
export type ConversationResponseEvent = {
message?: Message
conversation_id?: string
error?: string | null
}
export type Message = {
id: string
content: MessageContent
role: string
user: string | null
create_time: string | null
update_time: string | null
end_turn: null
weight: number
recipient: string
metadata: MessageMetadata
}
export type MessageContent = {
content_type: string
parts: string[]
}
export type MessageMetadata = any
import { remark } from 'remark'
import stripMarkdown from 'strip-markdown'
export function markdownToText(markdown?: string): string {
return remark()
.use(stripMarkdown)
.processSync(markdown ?? '')
.toString()
}
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