diff --git a/legacy/src/chatgpt-api.ts b/legacy/src/chatgpt-api.ts index 8a4bf3e6..8022b4f0 100644 --- a/legacy/src/chatgpt-api.ts +++ b/legacy/src/chatgpt-api.ts @@ -16,27 +16,32 @@ export class ChatGPTAPI { protected _sessionToken: string protected _clearanceToken: string protected _markdown: boolean + protected _debug: boolean protected _apiBaseUrl: string protected _backendApiBaseUrl: string protected _userAgent: string protected _headers: Record + protected _user: types.User | null = null // Stores access tokens for `accessTokenTTL` milliseconds before needing to refresh protected _accessTokenCache: ExpiryMap - protected _user: types.User | null = null - /** * Creates a new client wrapper around the unofficial ChatGPT REST API. * * @param opts.sessionToken = **Required** OpenAI session token which can be found in a valid session's cookies (see readme for instructions) + * @param opts.clearanceToken = **Required** Cloudflare `cf_clearance` cookie value (see readme for instructions) * @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 * @param accessTokenTTL - Optional override; how long in milliseconds access tokens should last before being forcefully refreshed + * @param accessToken - Optional default access token if you already have a valid one generated + * @param heaaders - Optional additional HTTP headers to be added to each `fetch` request + * @param debug - Optional enables logging debugging into to stdout */ constructor(opts: { sessionToken: string + clearanceToken: string /** @defaultValue `true` **/ @@ -48,15 +53,20 @@ export class ChatGPTAPI { /** @defaultValue `'https://chat.openai.com/backend-api'` **/ 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'` **/ + /** @defaultValue `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'` **/ userAgent?: string - /** @defaultValue 1 hour */ + /** @defaultValue 1 hour **/ accessTokenTTL?: number + /** @defaultValue `undefined` **/ accessToken?: string + /** @defaultValue `undefined` **/ headers?: Record + + /** @defaultValue `false` **/ + debug?: boolean }) { const { sessionToken, @@ -67,12 +77,14 @@ export class ChatGPTAPI { userAgent = USER_AGENT, accessTokenTTL = 60 * 60000, // 1 hour accessToken, - headers + headers, + debug = false } = opts this._sessionToken = sessionToken this._clearanceToken = clearanceToken this._markdown = !!markdown + this._debug = !!debug this._apiBaseUrl = apiBaseUrl this._backendApiBaseUrl = backendApiBaseUrl this._userAgent = userAgent @@ -124,6 +136,8 @@ export class ChatGPTAPI { * @param message - The prompt message to send * @param opts.conversationId - Optional ID of a conversation to continue * @param opts.parentMessageId - Optional ID of the previous message in the conversation + * @param opts.messageId - Optional ID of the message to send (defaults to a random UUID) + * @param opts.action - Optional ChatGPT `action` (either `next` or `variant`) * @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout) * @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated * @param opts.onConversationResponse - Optional callback which will be invoked every time the partial response is updated with the full conversation response @@ -138,6 +152,8 @@ export class ChatGPTAPI { const { conversationId, parentMessageId = uuidv4(), + messageId = uuidv4(), + action = 'next', timeoutMs, onProgress, onConversationResponse @@ -154,10 +170,10 @@ export class ChatGPTAPI { const accessToken = await this.refreshAccessToken() const body: types.ConversationJSONBody = { - action: 'next', + action, messages: [ { - id: uuidv4(), + id: messageId, role: 'user', content: { content_type: 'text', @@ -173,19 +189,25 @@ export class ChatGPTAPI { body.conversation_id = conversationId } - const url = `${this._backendApiBaseUrl}/conversation` let response = '' const responseP = new Promise((resolve, reject) => { + const url = `${this._backendApiBaseUrl}/conversation` + const headers = { + ...this._headers, + Authorization: `Bearer ${accessToken}`, + Accept: 'text/event-stream', + 'Content-Type': 'application/json', + Cookie: `cf_clearance=${this._clearanceToken}` + } + + if (this._debug) { + console.log('POST', url, { body, headers }) + } + fetchSSE(url, { method: 'POST', - headers: { - ...this._headers, - Authorization: `Bearer ${accessToken}`, - Accept: 'text/event-stream', - 'Content-Type': 'application/json', - Cookie: `cf_clearance=${this._clearanceToken}` - }, + headers, body: JSON.stringify(body), signal: abortSignal, onMessage: (data: string) => { @@ -282,14 +304,18 @@ export class ChatGPTAPI { let response: Response try { + const url = `${this._apiBaseUrl}/auth/session` const headers = { ...this._headers, cookie: `cf_clearance=${this._clearanceToken}; __Secure-next-auth.session-token=${this._sessionToken}`, accept: '*/*' } - console.log(`${this._apiBaseUrl}/auth/session`, headers) - const res = await fetch(`${this._apiBaseUrl}/auth/session`, { + if (this._debug) { + console.log('GET', url, headers) + } + + const res = await fetch(url, { headers }).then((r) => { response = r @@ -339,6 +365,10 @@ export class ChatGPTAPI { this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken) return accessToken } catch (err: any) { + if (this._debug) { + console.error(err) + } + const error = new types.ChatGPTError( `ChatGPT failed to refresh auth token. ${err.toString()}` ) diff --git a/legacy/src/types.ts b/legacy/src/types.ts index 61023e74..920f073f 100644 --- a/legacy/src/types.ts +++ b/legacy/src/types.ts @@ -273,10 +273,13 @@ export type MessageContent = { } export type MessageMetadata = any +export type MessageActionType = 'next' | 'variant' export type SendMessageOptions = { conversationId?: string parentMessageId?: string + messageId?: string + action?: MessageActionType timeoutMs?: number onProgress?: (partialResponse: string) => void onConversationResponse?: (response: ConversationResponseEvent) => void