chatgpt.mjs

javascriptCreated 3 Apr 2026, 03:32126 views
Access chatgpt without login, support continuing chat, streaming, web search, and uploading images.
#ai#realtime#chatbot
javascript
/***
  @ 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();
*/