Merge remote-tracking branch 'origin/main'

# Conflicts:
#	pnpm-lock.yaml
pull/19/head
GoneTone 2022-12-06 08:18:09 +08:00
commit 3de6f5ab3f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B9068CA583835DB2
13 zmienionych plików z 975 dodań i 166 usunięć

Wyświetl plik

@ -48,4 +48,6 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Run test - name: Run test
env:
SESSION_TOKEN: 'fake-session-token-for-CI'
run: pnpm run test run: pnpm run test

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 144 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 122 KiB

Wyświetl plik

@ -56,7 +56,7 @@ Hide
Require npx Require npx
#Set Shell bash #Set Shell bash
Set FontSize 17 Set FontSize 22
Set Width 1200 Set Width 1200
Set Height 600 Set Height 600
Set Padding 24 Set Padding 24
@ -67,7 +67,7 @@ Type " "
Sleep 980ms Sleep 980ms
Backspace 1 Backspace 1
Show Show
Type "npx tsx src/example.ts" Type "npx tsx src/demo.ts"
Sleep 500ms Sleep 500ms
Enter Enter
Show Show

Wyświetl plik

@ -1,6 +1,6 @@
{ {
"name": "chatgpt", "name": "chatgpt",
"version": "1.1.3", "version": "1.2.0",
"description": "Node.js client for the unofficial ChatGPT API.", "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",
@ -32,6 +32,7 @@
"prepare": "husky install", "prepare": "husky install",
"pre-commit": "lint-staged", "pre-commit": "lint-staged",
"test": "run-p test:*", "test": "run-p test:*",
"test:unit": "ava",
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check" "test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
}, },
"dependencies": { "dependencies": {
@ -47,6 +48,7 @@
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.2",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"ava": "^5.1.0",
"del-cli": "^5.0.0", "del-cli": "^5.0.0",
"dotenv-safe": "^8.2.0", "dotenv-safe": "^8.2.0",
"husky": "^8.0.2", "husky": "^8.0.2",
@ -65,6 +67,14 @@
"prettier --write" "prettier --write"
] ]
}, },
"ava": {
"extensions": {
"ts": "module"
},
"nodeArguments": [
"--loader=tsx"
]
},
"keywords": [ "keywords": [
"openai", "openai",
"chatgpt", "chatgpt",

Plik diff jest za duży Load Diff

Wyświetl plik

@ -9,10 +9,10 @@
[![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)
- [Intro](#intro) - [Intro](#intro)
- [How it works](#how-it-works)
- [Install](#install) - [Install](#install)
- [Usage](#usage) - [Usage](#usage)
- [Docs](#docs) - [Docs](#docs)
- [How it works](#how-it-works)
- [Examples](#examples) - [Examples](#examples)
- [Credit](#credit) - [Credit](#credit)
- [License](#license) - [License](#license)
@ -23,34 +23,10 @@ This package is a Node.js wrapper around [ChatGPT](https://openai.com/blog/chatg
You can use it to start building projects powered by ChatGPT like chatbots, websites, etc... You can use it to start building projects powered by ChatGPT like chatbots, websites, etc...
## How it works
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.
> **Note**
> This package will switch to using the official API once it's released.
> **Note**
> 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
```bash ```bash
npm install --save chatgpt npm install chatgpt
# or
yarn add chatgpt
# or
pnpm add chatgpt
``` ```
## Usage ## Usage
@ -59,9 +35,10 @@ pnpm add chatgpt
import { ChatGPTAPI } from 'chatgpt' import { ChatGPTAPI } from 'chatgpt'
async function example() { async function example() {
// sessionToken is required; see below for details
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN }) const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
// ensure the API is properly authenticated (optional) // ensure the API is properly authenticated
await api.ensureAuth() await api.ensureAuth()
// send a message and wait for the response // send a message and wait for the response
@ -83,31 +60,53 @@ const api = new ChatGPTAPI({
}) })
``` ```
A full [example](./src/example.ts) is included for testing purposes: A full [demo](./src/demo.ts) is included for testing purposes:
```bash ```bash
# 1. clone repo # 1. clone repo
# 2. install node deps # 2. install node deps
# 3. set `SESSION_TOKEN` in .env # 3. set `SESSION_TOKEN` in .env
# 4. run: # 4. run:
npx tsx src/example.ts npx tsx src/demo.ts
``` ```
## Docs ## Docs
See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods and parameters. See the [auto-generated docs](./docs/classes/ChatGPTAPI.md) for more info on methods and parameters.
## How it works
**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.
> **Note**
> This package will switch to using the official API once it's released.
> **Note**
> 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).
## Examples ## Examples
All of these awesome projects use the `chatgpt` package. 🤯 All of these awesome projects are built using the `chatgpt` package. 🤯
- [Twitter Bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot) powered by ChatGPT ✨ - [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 - 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)) - [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)) - [VSCode Extension](https://github.com/mpociot/chatgpt-vscode) ([demo](https://twitter.com/marcelpociot/status/1599180144551526400))
- [Go Telegram Bot](https://github.com/m1guelpf/chatgpt-telegram) - [Go Telegram Bot](https://github.com/m1guelpf/chatgpt-telegram)
- [Github ProBot](https://github.com/oceanlvr/ChatGPTBot) - [GitHub ProBot](https://github.com/oceanlvr/ChatGPTBot)
- [Discord Bot](https://github.com/onury5506/Discord-ChatGPT-Bot)
- [Lovelines.xyz](https://lovelines.xyz) - [Lovelines.xyz](https://lovelines.xyz)
- [EXM smart contracts](https://github.com/decentldotland/molecule)
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.

Wyświetl plik

@ -0,0 +1,67 @@
import test from 'ava'
import dotenv from 'dotenv-safe'
import { ChatGPTAPI } from './chatgpt-api'
dotenv.config()
const isCI = !!process.env.CI
test('ChatGPTAPI invalid session token', async (t) => {
t.throws(() => new ChatGPTAPI({ sessionToken: null }), {
message: 'ChatGPT invalid session token'
})
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({ sessionToken: 'invalid' })
await chatgpt.ensureAuth()
},
{
message: 'ChatGPT failed to refresh auth token. Error: Unauthorized'
}
)
})
test('ChatGPTAPI valid session token', async (t) => {
if (!isCI) {
t.timeout(2 * 60 * 1000) // 2 minutes
}
t.notThrows(
() => new ChatGPTAPI({ sessionToken: 'fake valid session token' })
)
await t.notThrowsAsync(
(async () => {
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
// Don't make any real API calls using our session token if we're running on CI
if (!isCI) {
await api.ensureAuth()
const response = await api.sendMessage('test')
console.log('chatgpt response', response)
t.truthy(response)
t.is(typeof response, 'string')
}
})()
)
})
if (!isCI) {
test('ChatGPTAPI expired session token', async (t) => {
const expiredSessionToken = process.env.TEST_EXPIRED_SESSION_TOKEN
await t.throwsAsync(
async () => {
const chatgpt = new ChatGPTAPI({ sessionToken: expiredSessionToken })
await chatgpt.ensureAuth()
},
{
message:
'ChatGPT failed to refresh auth token. Error: session token has expired'
}
)
})
}

Wyświetl plik

@ -1,9 +1,9 @@
import { createParser } from 'eventsource-parser'
import ExpiryMap from 'expiry-map' import ExpiryMap from 'expiry-map'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as types from './types' import * as types from './types'
import { fetch } from './fetch'
import { fetchSSE } from './fetch-sse'
import { markdownToText } from './utils' import { markdownToText } from './utils'
const KEY_ACCESS_TOKEN = 'accessToken' const KEY_ACCESS_TOKEN = 'accessToken'
@ -119,7 +119,7 @@ export class ChatGPTAPI {
let response = '' let response = ''
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._fetchSSE(url, { fetchSSE(url, {
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
@ -179,34 +179,22 @@ export class ChatGPTAPI {
const accessToken = res?.accessToken const accessToken = res?.accessToken
if (!accessToken) { if (!accessToken) {
console.warn('no auth token', res)
throw new Error('Unauthorized') throw new Error('Unauthorized')
} }
const error = res?.error
if (error) {
if (error === 'RefreshAccessTokenError') {
throw new Error('session token has expired')
} else {
throw new Error(error)
}
}
this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken) this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken)
return accessToken return accessToken
} catch (err: any) { } catch (err: any) {
throw new Error(`ChatGPT failed to refresh auth token: ${err.toString()}`) throw new Error(`ChatGPT failed to refresh auth token. ${err.toString()}`)
} }
} }
protected async _fetchSSE(
url: string,
options: Parameters<typeof fetch>[1] & { onMessage: (data: string) => void }
) {
const { onMessage, ...fetchOptions } = options
const resp = await fetch(url, fetchOptions)
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
resp.body.on('readable', () => {
let chunk: string | Buffer
while (null !== (chunk = resp.body.read())) {
parser.feed(chunk.toString())
}
})
}
} }

Wyświetl plik

@ -1,12 +1,16 @@
import dotenv from 'dotenv-safe' import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora' import { oraPromise } from 'ora'
import { ChatGPTAPI } from './chatgpt-api' import { ChatGPTAPI } from '.'
dotenv.config() dotenv.config()
/** /**
* Example CLI for testing functionality. * Example CLI for testing functionality.
*
* ```
* npx tsx src/demo.ts
* ```
*/ */
async function main() { async function main() {
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN }) const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })

31
src/fetch-sse.ts 100644
Wyświetl plik

@ -0,0 +1,31 @@
import { createParser } from 'eventsource-parser'
import { fetch } from './fetch'
// import { streamAsyncIterable } from './stream-async-iterable'
export async function fetchSSE(
url: string,
options: Parameters<typeof fetch>[1] & { onMessage: (data: string) => void }
) {
const { onMessage, ...fetchOptions } = options
const resp = await fetch(url, fetchOptions)
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
resp.body.on('readable', () => {
let chunk: string | Buffer
while (null !== (chunk = resp.body.read())) {
parser.feed(chunk.toString())
}
})
// TODO: add support for web-compatible `fetch`
// for await (const chunk of streamAsyncIterable(resp.body)) {
// const str = new TextDecoder().decode(chunk)
// parser.feed(str)
// }
}

3
src/fetch.ts 100644
Wyświetl plik

@ -0,0 +1,3 @@
import fetch from 'node-fetch'
export { fetch }

Wyświetl plik

@ -0,0 +1,16 @@
import { type ReadableStream } from 'stream/web'
export async function* streamAsyncIterable(stream: ReadableStream) {
const reader = stream.getReader()
try {
while (true) {
const { done, value } = await reader.read()
if (done) {
return
}
yield value
}
} finally {
reader.releaseLock()
}
}

Wyświetl plik

@ -7,7 +7,7 @@ export type Role = 'user' | 'assistant'
*/ */
export type SessionResult = { export type SessionResult = {
/** /**
* Object of the current user * Authenticated user
*/ */
user: User user: User
@ -20,6 +20,11 @@ export type SessionResult = {
* The access token * The access token
*/ */
accessToken: string accessToken: string
/**
* If there was an error associated with this request
*/
error?: string | null
} }
export type User = { export type User = {