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
|
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
|
||||||
|
|
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
|
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
|
||||||
|
|
12
package.json
12
package.json
|
@ -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",
|
||||||
|
|
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)
|
[](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)
|
- [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`.
|
|
||||||

|
|
||||||
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`.
|
||||||
|

|
||||||
|
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.
|
||||||
|
|
||||||
|
|
|
@ -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 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())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 })
|
|
@ -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 = {
|
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 = {
|
||||||
|
|
Ładowanie…
Reference in New Issue