Skip to content

Instantly share code, notes, and snippets.

@duke79
Last active March 26, 2023 05:59
Show Gist options
  • Save duke79/884339612f6277f56cb2cac24a267011 to your computer and use it in GitHub Desktop.
Save duke79/884339612f6277f56cb2cac24a267011 to your computer and use it in GitHub Desktop.
OpenAI Websocket Client
(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