Last active
March 26, 2023 05:59
-
-
Save duke79/884339612f6277f56cb2cac24a267011 to your computer and use it in GitHub Desktop.
OpenAI Websocket Client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(async () => { | |
class ChatGPT { | |
constructor() { | |
this.accessToken = ""; | |
this.userAgent = | |
"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"; | |
this.history = new Map(); | |
this.currentConversation = null; | |
} | |
async initWebSocket() { | |
const ws = new WebSocket("ws://localhost:8080"); | |
ws.addEventListener("open", () => { | |
console.log("WebSocket connection established."); | |
}); | |
ws.addEventListener("message", async (event) => { | |
await this.handleWebSocketMessage(ws, event); | |
}); | |
ws.addEventListener("close", () => { | |
console.log("WebSocket connection closed."); | |
}); | |
} | |
async handleWebSocketMessage(ws, event) { | |
console.log("WebSocket message received: " + event.data); | |
const message = JSON.parse(JSON.parse(event.data).text); | |
console.log("message", message); | |
if (!this.currentConversation?.[1].id) { | |
console.log("current conversation not found, loading..."); | |
await this.loadCurrentConversation(); | |
} | |
console.log("current conversation", this.currentConversation); | |
const conversation = await this.sendMessage({ | |
action: "next", | |
message: { | |
id: crypto.randomUUID(), | |
message: message.message, | |
}, | |
conversationId: message.conversationId || undefined, | |
parentMessageId: message.messageId, | |
}); | |
console.log("conversation", conversation); | |
// TODO: escape string so that it can be parsed by JSON.parse | |
// ws.send(JSON.stringify(conversation, null, 2)); | |
ws.send(JSON.stringify(conversation)); | |
// if url not https://chat.openai.com/chat/${conversation.conversationId}, redirect | |
const conversationUrl = `https://chat.openai.com/chat/${conversation.conversationId}`; | |
// if (conversationUrl !== location.href) { | |
console.log("redirecting to", conversationUrl); | |
location.href = conversationUrl; | |
// } | |
} | |
async ensureAuth() { | |
await this.refreshAccessToken(); | |
} | |
async refreshAccessToken() { | |
const response = await fetch("https://chat.openai.com/api/auth/session", { | |
"headers": { | |
"accept": "*/*", | |
"accept-language": "en-US,en;q=0.9,hi;q=0.8,mr;q=0.7", | |
"sec-ch-ua": "\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"", | |
"sec-ch-ua-mobile": "?1", | |
"sec-ch-ua-platform": "\"Android\"", | |
"sec-fetch-dest": "empty", | |
"sec-fetch-mode": "cors", | |
"sec-fetch-site": "same-origin" | |
}, | |
"referrerPolicy": "same-origin", | |
"body": null, | |
"method": "GET", | |
"mode": "cors", | |
"credentials": "include" | |
}); | |
const result = await response.json(); | |
const accessToken = result.accessToken; | |
if (!accessToken) { | |
throw new Error('unauthorized'); | |
} | |
console.log('accessToken', accessToken); | |
this.accessToken = accessToken; | |
} | |
async sendMessage(conversation) { | |
await this.ensureAuth(); | |
const url = new URL('https://chat.openai.com/backend-api/conversation'); | |
const headers = new Headers({ | |
'Content-Type': 'application/json', | |
Authorization: `Bearer ${this.accessToken}`, | |
'User-Agent': this.userAgent | |
}); | |
const { | |
action, | |
parentMessageId, | |
conversationId, | |
message | |
} = conversation; | |
console.log('conversation', conversation); | |
const body = { | |
conversation_id: conversationId, | |
action, | |
messages: message | |
? [ | |
{ | |
author: { | |
role: 'user' | |
}, | |
id: message.id, | |
role: 'user', | |
content: { | |
content_type: 'text', | |
parts: [message.message] | |
} | |
} | |
] | |
: undefined, | |
parent_message_id: parentMessageId, | |
model: 'text-davinci-002-render-sha' | |
}; | |
console.log('body', body); | |
const response = await fetch(url, { | |
method: 'POST', | |
headers: headers, | |
body: JSON.stringify(body) | |
}); | |
const textResponse = await response.text(); | |
const parsedResponse = this.parseStreamedGptResponse(textResponse); | |
return { | |
...parsedResponse, | |
title: this.currentConversation?.[1].title, | |
}; | |
} | |
parseStreamedGptResponse(data) { | |
function escapeRegExp(string) { | |
// The $& at the end means the whole matched string | |
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
} | |
function replaceAll(str, find, replace) { | |
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); | |
} | |
const chunks = data.split('data: '); | |
const sanitizedChunks = chunks | |
.map((c) => replaceAll(c, '\n', '')) | |
.filter((c) => !!c && c !== '[DONE]'); | |
if (!sanitizedChunks.length) { | |
return null; | |
} | |
// @ts-ignore | |
const response = JSON.parse(sanitizedChunks[sanitizedChunks.length - 1]); | |
return { | |
message: response.message.content.parts[0], | |
messageId: response.message.id, | |
conversationId: response.conversation_id, | |
isDone: response.message?.end_turn === true, | |
}; | |
} | |
async loadCurrentConversation() { | |
this.currentConversation = await this.getConversation( | |
location.pathname.split('/')[2] | |
); | |
console.log('currentConversation', this.currentConversation); | |
} | |
async getConversation(conversationId) { | |
await this.ensureAuth(); | |
if (conversationId) { | |
const url = `https://chat.openai.com/backend-api/conversation/${conversationId}`; | |
const [ok, json, response] = await this.apiGetRequest(url); | |
if (ok) { | |
json.id = conversationId; | |
return [ok, json, response]; | |
} else { | |
return this.handleError( | |
json, | |
response, | |
`Failed to get conversation ${conversationId}` | |
); | |
} | |
} | |
} | |
async loadHistory() { | |
const [ok, history, response] = await this.getHistory(); | |
if (ok) { | |
this.history = history; | |
} else { | |
return this.handleError(json, response, 'Failed to load history'); | |
} | |
console.log('history', this.history); | |
} | |
async getHistory(limit = 20, offset = 0) { | |
await this.ensureAuth(); | |
const url = 'https://chat.openai.com/backend-api/conversations'; | |
const queryParams = new URLSearchParams({ offset, limit }).toString(); | |
const [ok, json, response] = await this.apiGetRequest( | |
`${url}?${queryParams}` | |
); | |
if (ok) { | |
const history = {}; | |
json.items.forEach(item => { | |
item.createdTime = new Date(item.createTime); | |
delete item.createTime; | |
history[item.id] = item; | |
}); | |
return [ok, history, 'Retrieved history']; | |
} else { | |
return this.handleError(json, response, 'Failed to get history'); | |
} | |
} | |
async apiGetRequest(url) { | |
const response = await fetch(url, { | |
method: 'GET', | |
headers: { | |
'Content-Type': 'application/json', | |
Authorization: `Bearer ${this.accessToken}` | |
} | |
}); | |
const json = await response.json(); | |
return [response.ok, json, response]; | |
} | |
handleError(json, response, defaultMessage) { | |
console.error(json); | |
} | |
async apiPatchRequest(url, data) { | |
await this.ensureAuth(); | |
const response = await fetch(url, { | |
method: 'PATCH', | |
headers: { | |
'Content-Type': 'application/json', | |
Authorization: `Bearer ${this.accessToken}` | |
}, | |
body: JSON.stringify(data) | |
}); | |
const json = await response.json(); | |
return [response.ok, json, response]; | |
} | |
async setTitle(conversationId, title) { | |
const url = `https://chat.openai.com/backend-api/conversation/${conversationId}`; | |
const data = { title }; | |
const [ok, json, response] = await this.apiPatchRequest(url, data); | |
if (ok) { | |
return [ok, json, 'Title set']; | |
} else { | |
return this.handleError(json, response, 'Failed to set title'); | |
} | |
} | |
} | |
const chatGPT = new ChatGPT(); | |
await chatGPT.loadCurrentConversation(); | |
await chatGPT.loadHistory(); | |
chatGPT.initWebSocket(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment