kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
Merge remote-tracking branch 'origin/main'
# Conflicts: # pnpm-lock.yamlpull/19/head
commit
3de6f5ab3f
|
@ -48,4 +48,6 @@ jobs:
|
|||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run test
|
||||
env:
|
||||
SESSION_TOKEN: 'fake-session-token-for-CI'
|
||||
run: pnpm run test
|
||||
|
|
BIN
media/demo.gif
BIN
media/demo.gif
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 144 KiB Po Szerokość: | Wysokość: | Rozmiar: 122 KiB |
|
@ -56,7 +56,7 @@ Hide
|
|||
Require npx
|
||||
|
||||
#Set Shell bash
|
||||
Set FontSize 17
|
||||
Set FontSize 22
|
||||
Set Width 1200
|
||||
Set Height 600
|
||||
Set Padding 24
|
||||
|
@ -67,7 +67,7 @@ Type " "
|
|||
Sleep 980ms
|
||||
Backspace 1
|
||||
Show
|
||||
Type "npx tsx src/example.ts"
|
||||
Type "npx tsx src/demo.ts"
|
||||
Sleep 500ms
|
||||
Enter
|
||||
Show
|
||||
|
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "chatgpt",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"description": "Node.js client for the unofficial ChatGPT API.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"repository": "transitive-bullshit/chatgpt-api",
|
||||
|
@ -32,6 +32,7 @@
|
|||
"prepare": "husky install",
|
||||
"pre-commit": "lint-staged",
|
||||
"test": "run-p test:*",
|
||||
"test:unit": "ava",
|
||||
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -47,6 +48,7 @@
|
|||
"@types/node": "^18.11.9",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"ava": "^5.1.0",
|
||||
"del-cli": "^5.0.0",
|
||||
"dotenv-safe": "^8.2.0",
|
||||
"husky": "^8.0.2",
|
||||
|
@ -65,6 +67,14 @@
|
|||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"ava": {
|
||||
"extensions": {
|
||||
"ts": "module"
|
||||
},
|
||||
"nodeArguments": [
|
||||
"--loader=tsx"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"openai",
|
||||
"chatgpt",
|
||||
|
|
894
pnpm-lock.yaml
894
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
61
readme.md
61
readme.md
|
@ -9,10 +9,10 @@
|
|||
[](https://www.npmjs.com/package/chatgpt) [](https://github.com/transitive-bullshit/chatgpt-api/actions/workflows/test.yml) [](https://github.com/transitive-bullshit/chatgpt-api/blob/main/license) [](https://prettier.io)
|
||||
|
||||
- [Intro](#intro)
|
||||
- [How it works](#how-it-works)
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Docs](#docs)
|
||||
- [How it works](#how-it-works)
|
||||
- [Examples](#examples)
|
||||
- [Credit](#credit)
|
||||
- [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...
|
||||
|
||||
## 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`.
|
||||

|
||||
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
|
||||
|
||||
```bash
|
||||
npm install --save chatgpt
|
||||
# or
|
||||
yarn add chatgpt
|
||||
# or
|
||||
pnpm add chatgpt
|
||||
npm install chatgpt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -59,9 +35,10 @@ pnpm add chatgpt
|
|||
import { ChatGPTAPI } from 'chatgpt'
|
||||
|
||||
async function example() {
|
||||
// sessionToken is required; see below for details
|
||||
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()
|
||||
|
||||
// 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
|
||||
# 1. clone repo
|
||||
# 2. install node deps
|
||||
# 3. set `SESSION_TOKEN` in .env
|
||||
# 4. run:
|
||||
npx tsx src/example.ts
|
||||
npx tsx src/demo.ts
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
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`.
|
||||

|
||||
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
|
||||
|
||||
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 ✨
|
||||
- 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)
|
||||
- [GitHub ProBot](https://github.com/oceanlvr/ChatGPTBot)
|
||||
- [Discord Bot](https://github.com/onury5506/Discord-ChatGPT-Bot)
|
||||
- [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.
|
||||
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { createParser } from 'eventsource-parser'
|
||||
import ExpiryMap from 'expiry-map'
|
||||
import fetch from 'node-fetch'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import * as types from './types'
|
||||
import { fetch } from './fetch'
|
||||
import { fetchSSE } from './fetch-sse'
|
||||
import { markdownToText } from './utils'
|
||||
|
||||
const KEY_ACCESS_TOKEN = 'accessToken'
|
||||
|
@ -119,7 +119,7 @@ export class ChatGPTAPI {
|
|||
let response = ''
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._fetchSSE(url, {
|
||||
fetchSSE(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
|
@ -179,34 +179,22 @@ export class ChatGPTAPI {
|
|||
const accessToken = res?.accessToken
|
||||
|
||||
if (!accessToken) {
|
||||
console.warn('no auth token', res)
|
||||
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)
|
||||
return accessToken
|
||||
} 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import dotenv from 'dotenv-safe'
|
||||
import { oraPromise } from 'ora'
|
||||
|
||||
import { ChatGPTAPI } from './chatgpt-api'
|
||||
import { ChatGPTAPI } from '.'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
/**
|
||||
* Example CLI for testing functionality.
|
||||
*
|
||||
* ```
|
||||
* npx tsx src/demo.ts
|
||||
* ```
|
||||
*/
|
||||
async function main() {
|
||||
const api = new ChatGPTAPI({ sessionToken: process.env.SESSION_TOKEN })
|
|
@ -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)
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import fetch from 'node-fetch'
|
||||
|
||||
export { fetch }
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ export type Role = 'user' | 'assistant'
|
|||
*/
|
||||
export type SessionResult = {
|
||||
/**
|
||||
* Object of the current user
|
||||
* Authenticated user
|
||||
*/
|
||||
user: User
|
||||
|
||||
|
@ -20,6 +20,11 @@ export type SessionResult = {
|
|||
* The access token
|
||||
*/
|
||||
accessToken: string
|
||||
|
||||
/**
|
||||
* If there was an error associated with this request
|
||||
*/
|
||||
error?: string | null
|
||||
}
|
||||
|
||||
export type User = {
|
||||
|
|
Ładowanie…
Reference in New Issue