chatgpt.mjs
javascript•Created 3 Apr 2026, 03:32•126 views
Access chatgpt without login, support continuing chat, streaming, web search, and uploading images.
#ai#realtime#chatbot
javascript
0 lines
/***
@ Base: https://chatgpt.com/
@ Author: Shannz
@ Note: Access chatgpt without login, support continuing chat, streaming, web search, and uploading images.
***/
import crypto from "crypto";
import fs from "fs";
import path from "path";
let _fetch = globalThis.fetch;
if (!_fetch) {
const { fetch } = await import("undici");
_fetch = fetch;
}
let _perf = globalThis.performance;
if (!_perf) {
const { performance } = await import("perf_hooks");
_perf = performance;
}
export class ChatGPT {
constructor(cfg = {}) {
this.baseUrl = "https://chatgpt.com";
this.userAgent = cfg.userAgent ?? "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36";
this.oaiDid = cfg.did ?? crypto.randomUUID();
this.screenWidth = cfg.screenWidth ?? 423;
this.screenHeight = cfg.screenHeight ?? 965;
this.lang = cfg.lang ?? "id-ID";
this.buildNumber = cfg.buildNumber ?? "prod-69a06c53754594935887d6c16b844885964a78fc";
this.authToken = cfg.authToken ?? null;
}
async send(message, opts = {}) {
if (!message?.trim()) throw new Error("Pesan tidak boleh kosong.");
const {
conversationId = null,
parentMessageId = null,
imagePath = null,
webSearch = false,
stream = false,
onChunk,
} = opts;
const parentMsgId = parentMessageId ?? "client-created-root";
const msgId = crypto.randomUUID();
let image = null;
if (imagePath) {
image = await this._uploadImage(imagePath, { conversationId, parentMsgId });
}
const [tokens, conduitToken] = await Promise.all([
this._generateSentinelTokens(),
this._getConduitToken(message, msgId, { conversationId, parentMsgId, webSearch, image }),
]);
const body = this._buildMessageBody(message, msgId, {
conversationId, parentMsgId, webSearch, image,
});
const res = await _fetch(`${this.baseUrl}/backend-anon/f/conversation`, {
method: "POST",
body: JSON.stringify(body),
headers: this._headers({
"accept": "text/event-stream",
"OAI-Language": this.lang,
"OpenAI-Sentinel-Chat-Requirements-Token": tokens.chatRequirementsToken,
"OpenAI-Sentinel-Turnstile-Token": tokens.turnstile,
"OpenAI-Sentinel-Proof-Token": tokens.pow,
"X-Conduit-Token": conduitToken,
}),
});
if (!res.ok) {
const err = await res.text().catch(() => "(no body)");
throw new Error(`HTTP ${res.status}: ${err}`);
}
return this._parseSSE(res.body, { stream, onChunk });
}
async _uploadImage(filePath, ctx = {}) {
const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(filePath);
const mimeType = this._getMimeType(fileName);
const sizeBytes = fileBuffer.length;
const { width, height } = this._getImageDimensions(fileBuffer, mimeType);
const registerRes = await _fetch(`${this.baseUrl}/backend-anon/files`, {
method: "POST",
headers: this._headers(),
body: JSON.stringify({
file_name: fileName,
file_size: sizeBytes,
use_case: "multimodal",
timezone_offset_min: new Date().getTimezoneOffset(),
reset_rate_limits: false,
}),
}).then(r => r.json());
const { upload_url, file_id } = registerRes;
if (!upload_url || !file_id) {
throw new Error(`Gagal mendaftar file: ${JSON.stringify(registerRes)}`);
}
const uploadRes = await _fetch(upload_url, {
method: "PUT",
headers: {
"Content-Type": mimeType,
"x-ms-blob-type": "BlockBlob",
"x-ms-version": "2020-04-08",
},
body: fileBuffer,
});
if (!uploadRes.ok) {
throw new Error(`Upload blob gagal: HTTP ${uploadRes.status}`);
}
const processBody = {
file_id,
use_case: "multimodal",
index_for_retrieval: false,
file_name: fileName,
};
if (ctx.conversationId || ctx.parentMsgId) {
processBody.metadata = {
library_file_info: {
origination_message_id: ctx.parentMsgId ?? null,
origination_thread_id: ctx.conversationId ?? null,
},
};
}
const processRes = await _fetch(
`${this.baseUrl}/backend-anon/files/process_upload_stream`,
{ method: "POST", headers: this._headers(), body: JSON.stringify(processBody) }
);
const decoder = new TextDecoder();
let buf = "";
for await (const chunk of processRes.body) {
buf += decoder.decode(chunk, { stream: true });
if (buf.includes("file.processing.completed")) break;
}
return { fileId: file_id, fileName, mimeType, sizeBytes, width, height };
}
_headers(extra = {}) {
return {
"User-Agent": this.userAgent,
"accept": "*/*",
"accept-language": `${this.lang},en-US;q=0.9,en;q=0.8`,
"content-type": "application/json",
"OAI-Device-Id": this.oaiDid,
"sec-ch-ua": '"Chromium";v="144", "Not/A)Brand";v="24"',
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": '"Android"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"origin": "https://chatgpt.com",
"referer": "https://chatgpt.com/",
...(this.authToken ? { "authorization": `Bearer ${this.authToken}` } : {}),
...extra,
};
}
_fnv1a(str) {
let h = 2166136261;
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619) >>> 0;
}
h ^= h >>> 16; h = Math.imul(h, 2246822507) >>> 0;
h ^= h >>> 13; h = Math.imul(h, 3266489909) >>> 0;
h ^= h >>> 16;
return (h >>> 0).toString(16).padStart(8, "0");
}
_encodeConfig(cfg) {
return Buffer.from(JSON.stringify(cfg)).toString("base64");
}
_makeBrowserConfig() {
return [
this.screenWidth + this.screenHeight,
String(new Date()),
2172649472,
0,
this.userAgent,
null,
this.buildNumber,
this.lang,
`${this.lang},en`,
0,
"contacts\u2212[object ContactsManager]",
"_reactListening",
"User",
_perf.now(),
crypto.randomUUID(),
"",
8,
_perf.timeOrigin,
0, 0, 0, 0, 0, 0, 0,
];
}
_computePow(seed, difficulty, cfg) {
const start = _perf.now();
for (let i = 0; i < 500_000; i++) {
cfg[3] = i;
cfg[9] = Math.round(_perf.now() - start);
const encoded = this._encodeConfig(cfg);
if (this._fnv1a(seed + encoded).substring(0, difficulty.length) <= difficulty) {
return "gAAAAAB" + encoded + "~S";
}
}
return "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4De";
}
async _generateSentinelTokens() {
const initCfg = this._makeBrowserConfig();
initCfg[3] = 1; initCfg[9] = 0;
const initToken = "gAAAAAC" + this._encodeConfig(initCfg);
const prepareRes = await _fetch(
`${this.baseUrl}/backend-anon/sentinel/chat-requirements/prepare`,
{ method: "POST", headers: this._headers(), body: JSON.stringify({ p: initToken }) }
).then(r => r.json());
let pow = null;
if (prepareRes.proofofwork?.required) {
pow = this._computePow(
prepareRes.proofofwork.seed,
prepareRes.proofofwork.difficulty,
this._makeBrowserConfig()
);
}
const turnstile = crypto
.randomBytes(Math.floor((2256 / 4) * 3))
.toString("base64")
.slice(0, 2256);
const finalizeBody = { prepare_token: prepareRes.prepare_token ?? "" };
if (pow) finalizeBody.proofofwork = pow;
if (turnstile) finalizeBody.turnstile = turnstile;
const finalizeRes = await _fetch(
`${this.baseUrl}/backend-anon/sentinel/chat-requirements/finalize`,
{ method: "POST", headers: this._headers(), body: JSON.stringify(finalizeBody) }
).then(r => r.json());
return { pow, turnstile, chatRequirementsToken: finalizeRes.token ?? null };
}
async _getConduitToken(message, msgId, { conversationId, parentMsgId, webSearch, image }) {
const body = {
action: "next",
fork_from_shared_post: false,
parent_message_id: parentMsgId,
model: "auto",
timezone_offset_min: new Date().getTimezoneOffset(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
conversation_mode: { kind: "primary_assistant" },
system_hints: webSearch ? ["search"] : [],
supports_buffering: true,
supported_encodings: ["v1"],
partial_query: {
id: msgId,
author: { role: "user" },
content: { content_type: "text", parts: [message] },
},
client_contextual_info: { app_name: "chatgpt.com" },
};
if (conversationId) body.conversation_id = conversationId;
if (image) body.attachment_mime_types = [image.mimeType];
const res = await _fetch(
`${this.baseUrl}/backend-anon/f/conversation/prepare`,
{
method: "POST",
headers: this._headers({ "X-Conduit-Token": "no-token" }),
body: JSON.stringify(body),
}
);
const data = await res.json();
return data.token ?? data.conduit_token;
}
_buildMessageBody(message, msgId, { conversationId, parentMsgId, webSearch, image }) {
let content, msgMeta;
if (image) {
content = {
content_type: "multimodal_text",
parts: [
{
content_type: "image_asset_pointer",
asset_pointer: `file-service://${image.fileId}`,
size_bytes: image.sizeBytes,
width: image.width,
height: image.height,
},
message,
],
};
msgMeta = {
attachments: [{
id: image.fileId,
size: image.sizeBytes,
name: image.fileName,
mime_type: image.mimeType,
width: image.width,
height: image.height,
source: "local",
is_big_paste: false,
}],
selected_github_repos: [],
selected_all_github_repos: false,
serialization_metadata: { custom_symbol_offsets: [] },
};
} else {
content = { content_type: "text", parts: [message] };
msgMeta = {
selected_github_repos: [],
selected_all_github_repos: false,
serialization_metadata: { custom_symbol_offsets: [] },
...(webSearch ? { system_hints: ["search"] } : {}),
};
}
const body = {
action: "next",
messages: [{
id: msgId,
author: { role: "user" },
create_time: Date.now() / 1000,
content,
metadata: msgMeta,
}],
parent_message_id: parentMsgId,
model: "auto",
timezone_offset_min: new Date().getTimezoneOffset(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
conversation_mode: { kind: "primary_assistant" },
enable_message_followups: true,
system_hints: webSearch ? ["search"] : [],
supports_buffering: true,
supported_encodings: ["v1"],
client_contextual_info: {
is_dark_mode: true,
time_since_loaded: 10,
page_height: 845,
page_width: 423,
pixel_ratio: 1.7,
screen_height: this.screenHeight,
screen_width: this.screenWidth,
app_name: "chatgpt.com",
},
no_auth_ad_preferences: {
personalization_enabled: true,
history_enabled: true,
},
paragen_cot_summary_display_override: "allow",
force_parallel_switch: "auto",
};
if (conversationId) body.conversation_id = conversationId;
if (webSearch) {
body.force_use_search = true;
body.client_reported_search_source = "conversation_composer_web_icon";
}
return body;
}
async _parseSSE(body, { stream, onChunk }) {
const decoder = new TextDecoder();
let buf = "";
let fullText = "";
let title = null;
let model = null;
let convId = null;
let assistantMsgId = null;
for await (const chunk of body) {
buf += decoder.decode(chunk, { stream: true });
const lines = buf.split("\n");
buf = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const raw = line.slice(5).trim();
if (!raw || raw === "[DONE]") continue;
let json;
try { json = JSON.parse(raw); } catch { continue; }
if (json.conversation_id) {
convId = json.conversation_id;
} else if (json.v?.conversation_id) {
convId = json.v.conversation_id;
}
if (
json.v &&
!Array.isArray(json.v) &&
json.v.message?.author?.role === "assistant" &&
json.v.message?.id
) {
assistantMsgId = json.v.message.id;
}
if (json.type === "title_generation") title = json.title;
if (json.type === "server_ste_metadata") model = json.metadata?.model_slug ?? null;
const patches = Array.isArray(json.v) ? json.v : [];
for (const p of patches) {
if (p.o === "append" && p.p?.includes("/message/content/parts/0")) {
fullText += p.v;
if (stream && typeof onChunk === "function") onChunk(p.v);
}
}
}
}
return {
text: fullText,
title,
model,
conversationId: convId,
messageId: assistantMsgId,
};
}
_getMimeType(fileName) {
const ext = path.extname(fileName).toLowerCase();
return {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
}[ext] ?? "application/octet-stream";
}
_getImageDimensions(buffer, mimeType) {
try {
if (mimeType === "image/png") {
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
}
if (mimeType === "image/jpeg") {
let i = 2;
while (i < buffer.length - 8) {
if (buffer[i] !== 0xFF) break;
const marker = buffer[i + 1];
const segLen = buffer.readUInt16BE(i + 2);
if (marker >= 0xC0 && marker <= 0xC3) {
return { height: buffer.readUInt16BE(i + 5), width: buffer.readUInt16BE(i + 7) };
}
i += 2 + segLen;
}
}
} catch { }
return { width: 0, height: 0 };
}
}
/*
const chat = new ChatGPT({ lang: "id-ID" });
// New Chat
console.log("── Turn 1: Chat baru ──");
const r1 = await chat.send("Halo, namaku Shannz.");
console.log("Bot:", r1.text);
console.log("Model:", r1.model);
console.log("ConvID:", r1.conversationId);
console.log("MessageID:", r1.messageId);
// Continue Chat
console.log("\n── Turn 2: Lanjut chat ──");
const r2 = await chat.send("Siapa namaku tadi?", {
conversationId: r1.conversationId,
parentMessageId: r1.messageId,
});
console.log("Bot:", r2.text);
// New Chat with pictures
console.log("\n── Turn 3: Kirim gambar ──");
const r3 = await chat.send("Gambar apa ini?", {
imagePath: "./screenshot.png",
});
console.log("Bot:", r3.text);
console.log("ConvID:", r3.conversationId, "| MsgID:", r3.messageId);
// Continue Chat with pictures
console.log("\n── Turn 4: Lanjut chat gambar ──");
const r4 = await chat.send("Ceritakan lebih detail!", {
conversationId: r3.conversationId,
parentMessageId: r3.messageId,
});
console.log("Bot:", r4.text);
// Stream & Web Search Mode
process.stdout.write("\n── Turn 5: Streaming Mode ──\nBot: ");
await chat.send("Sebutkan 3 bahasa pemrograman!", {
conversationId: r2.conversationId,
parentMessageId: r2.messageId,
webSearch: true,
stream: true,
onChunk: (t) => process.stdout.write(t),
});
console.log();
*/