ai-zero-token 2.0.5 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +15 -12
- package/README.zh-CN.md +15 -12
- package/admin-ui/dist/assets/StatCard-7TEzqn2i.js +1 -0
- package/admin-ui/dist/assets/accounts-bCDKXGg9.js +4 -0
- package/admin-ui/dist/assets/{docs-Dh0aFha_.js → docs--eK_2fzC.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-C1M7-0q1.js → image-bed-7wBZ1GhS.js} +1 -1
- package/admin-ui/dist/assets/index-C22_3Mxq.css +1 -0
- package/admin-ui/dist/assets/index-CdFYy5j6.js +10 -0
- package/admin-ui/dist/assets/{launch-pB7YlWFI.js → launch-BiD1Khtg.js} +1 -1
- package/admin-ui/dist/assets/{logs-B7McijSi.js → logs-BdoKDqh2.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-Bx3XmXPk.js → network-detect-BvKns5nQ.js} +1 -1
- package/admin-ui/dist/assets/overview-wm6M45fu.js +1 -0
- package/admin-ui/dist/assets/settings-DOOu7Kd8.js +5 -0
- package/admin-ui/dist/assets/{tester-BG-up8qP.js → tester-NrARmlis.js} +1 -1
- package/admin-ui/dist/assets/usage-CdWRVMDV.js +1 -0
- package/admin-ui/dist/index.html +2 -2
- package/dist/core/context.js +3 -0
- package/dist/core/providers/http-client.js +21 -2
- package/dist/core/providers/openai-codex/chat.js +2 -1
- package/dist/core/providers/openai-codex/chatgpt-web-image.js +1404 -0
- package/dist/core/services/auth-service.js +51 -4
- package/dist/core/services/config-service.js +9 -0
- package/dist/core/services/image-service.js +31 -1
- package/dist/core/services/usage-service.js +349 -0
- package/dist/core/store/codex-auth-store.js +149 -15
- package/dist/core/store/settings-store.js +8 -2
- package/dist/core/store/state-paths.js +17 -1
- package/dist/server/app.js +848 -50
- package/docs/API_USAGE.md +33 -3
- package/package.json +1 -1
- package/admin-ui/dist/assets/accounts-ABMyXo4H.js +0 -4
- package/admin-ui/dist/assets/index--rNjdmzf.js +0 -10
- package/admin-ui/dist/assets/index-DjtN30PC.css +0 -1
- package/admin-ui/dist/assets/overview-CV0H2Nsq.js +0 -1
- package/admin-ui/dist/assets/settings-ynCIdUvZ.js +0 -7
|
@@ -0,0 +1,1404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
const CHATGPT_BASE_URL = "https://chatgpt.com";
|
|
4
|
+
const DEFAULT_CLIENT_VERSION = "prod-be885abbfcfe7b1f511e88b3003d9ee44757fbad";
|
|
5
|
+
const DEFAULT_CLIENT_BUILD_NUMBER = "5955942";
|
|
6
|
+
const DEFAULT_POW_SCRIPT = "https://chatgpt.com/backend-api/sentinel/sdk.js";
|
|
7
|
+
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0";
|
|
8
|
+
const DEFAULT_SEC_CH_UA = '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"';
|
|
9
|
+
const IMAGE_POLL_TIMEOUT_MS = 12e4;
|
|
10
|
+
const IMAGE_POLL_INTERVAL_MS = 4e3;
|
|
11
|
+
const CODEX_IMAGE_MODEL = "codex-gpt-image-2";
|
|
12
|
+
const SHA3_512_RATE_BYTES = 72;
|
|
13
|
+
const UINT64_MASK = (1n << 64n) - 1n;
|
|
14
|
+
const KECCAK_ROUND_CONSTANTS = [
|
|
15
|
+
0x0000000000000001n,
|
|
16
|
+
0x0000000000008082n,
|
|
17
|
+
0x800000000000808an,
|
|
18
|
+
0x8000000080008000n,
|
|
19
|
+
0x000000000000808bn,
|
|
20
|
+
0x0000000080000001n,
|
|
21
|
+
0x8000000080008081n,
|
|
22
|
+
0x8000000000008009n,
|
|
23
|
+
0x000000000000008an,
|
|
24
|
+
0x0000000000000088n,
|
|
25
|
+
0x0000000080008009n,
|
|
26
|
+
0x000000008000000an,
|
|
27
|
+
0x000000008000808bn,
|
|
28
|
+
0x800000000000008bn,
|
|
29
|
+
0x8000000000008089n,
|
|
30
|
+
0x8000000000008003n,
|
|
31
|
+
0x8000000000008002n,
|
|
32
|
+
0x8000000000000080n,
|
|
33
|
+
0x000000000000800an,
|
|
34
|
+
0x800000008000000an,
|
|
35
|
+
0x8000000080008081n,
|
|
36
|
+
0x8000000000008080n,
|
|
37
|
+
0x0000000080000001n,
|
|
38
|
+
0x8000000080008008n
|
|
39
|
+
];
|
|
40
|
+
const KECCAK_ROTATION_OFFSETS = [
|
|
41
|
+
[0, 36, 3, 41, 18],
|
|
42
|
+
[1, 44, 10, 45, 2],
|
|
43
|
+
[62, 6, 43, 15, 61],
|
|
44
|
+
[28, 55, 25, 21, 56],
|
|
45
|
+
[27, 20, 39, 8, 14]
|
|
46
|
+
];
|
|
47
|
+
const CORES = [8, 16, 24, 32];
|
|
48
|
+
const DOCUMENT_KEYS = ["_reactListeningo743lnnpvdg", "location"];
|
|
49
|
+
const NAVIGATOR_KEYS = [
|
|
50
|
+
"registerProtocolHandler-function registerProtocolHandler() { [native code] }",
|
|
51
|
+
"storage-[object StorageManager]",
|
|
52
|
+
"locks-[object LockManager]",
|
|
53
|
+
"appCodeName-Mozilla",
|
|
54
|
+
"permissions-[object Permissions]",
|
|
55
|
+
"share-function share() { [native code] }",
|
|
56
|
+
"webdriver-false",
|
|
57
|
+
"vendor-Google Inc.",
|
|
58
|
+
"mediaDevices-[object MediaDevices]",
|
|
59
|
+
"vibrate-function vibrate() { [native code] }",
|
|
60
|
+
"cookieEnabled-true",
|
|
61
|
+
"product-Gecko",
|
|
62
|
+
"credentials-[object CredentialsContainer]",
|
|
63
|
+
"keyboard-[object Keyboard]",
|
|
64
|
+
"gpu-[object GPU]",
|
|
65
|
+
"pdfViewerEnabled-true",
|
|
66
|
+
"language-zh-CN",
|
|
67
|
+
"geolocation-[object Geolocation]",
|
|
68
|
+
"hardwareConcurrency-32"
|
|
69
|
+
];
|
|
70
|
+
const WINDOW_KEYS = [
|
|
71
|
+
"0",
|
|
72
|
+
"window",
|
|
73
|
+
"self",
|
|
74
|
+
"document",
|
|
75
|
+
"name",
|
|
76
|
+
"location",
|
|
77
|
+
"customElements",
|
|
78
|
+
"history",
|
|
79
|
+
"navigation",
|
|
80
|
+
"innerWidth",
|
|
81
|
+
"innerHeight",
|
|
82
|
+
"screenX",
|
|
83
|
+
"screenY",
|
|
84
|
+
"outerWidth",
|
|
85
|
+
"outerHeight",
|
|
86
|
+
"devicePixelRatio",
|
|
87
|
+
"screen",
|
|
88
|
+
"chrome",
|
|
89
|
+
"navigator",
|
|
90
|
+
"performance",
|
|
91
|
+
"crypto",
|
|
92
|
+
"indexedDB",
|
|
93
|
+
"sessionStorage",
|
|
94
|
+
"localStorage",
|
|
95
|
+
"__NEXT_DATA__",
|
|
96
|
+
"__BUILD_MANIFEST"
|
|
97
|
+
];
|
|
98
|
+
const sessionIdsByProfile = /* @__PURE__ */ new Map();
|
|
99
|
+
const cookiesByProfile = /* @__PURE__ */ new Map();
|
|
100
|
+
let nativeSha3Supported;
|
|
101
|
+
let warnedAboutSha3Fallback = false;
|
|
102
|
+
function randomChoice(items) {
|
|
103
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
104
|
+
}
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
function unixSeconds() {
|
|
109
|
+
return Math.floor(Date.now() / 1e3);
|
|
110
|
+
}
|
|
111
|
+
function sleep(ms) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
function parseJson(value, context) {
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(value);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`${context} \u54CD\u5E94\u4E0D\u662F\u5408\u6CD5 JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function roundMs(value) {
|
|
122
|
+
return Math.round(value * 100) / 100;
|
|
123
|
+
}
|
|
124
|
+
function normalizeFetchHeaders(headers) {
|
|
125
|
+
const normalized = {};
|
|
126
|
+
headers.forEach((value, key) => {
|
|
127
|
+
normalized[key.toLowerCase()] = value;
|
|
128
|
+
});
|
|
129
|
+
const getSetCookie = headers.getSetCookie;
|
|
130
|
+
if (typeof getSetCookie === "function") {
|
|
131
|
+
const setCookies = getSetCookie.call(headers);
|
|
132
|
+
if (setCookies.length > 0) {
|
|
133
|
+
normalized["set-cookie"] = setCookies.join(",");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return normalized;
|
|
137
|
+
}
|
|
138
|
+
function parsePowResources(html) {
|
|
139
|
+
const scriptSources = Array.from(html.matchAll(/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi)).map((match) => match[1]).filter((value) => Boolean(value));
|
|
140
|
+
const dataBuild = scriptSources.map((source) => /c\/[^/]*\/_/i.exec(source)?.[0]).find(Boolean) ?? /<html[^>]*data-build=["']([^"']*)["']/i.exec(html)?.[1] ?? "";
|
|
141
|
+
return {
|
|
142
|
+
scriptSources: scriptSources.length > 0 ? scriptSources : [DEFAULT_POW_SCRIPT],
|
|
143
|
+
dataBuild
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function getWebSession(profile) {
|
|
147
|
+
const key = profile.accountId || profile.profileId;
|
|
148
|
+
const existing = sessionIdsByProfile.get(key);
|
|
149
|
+
if (existing) {
|
|
150
|
+
return existing;
|
|
151
|
+
}
|
|
152
|
+
const created = {
|
|
153
|
+
deviceId: randomUUID(),
|
|
154
|
+
sessionId: randomUUID()
|
|
155
|
+
};
|
|
156
|
+
sessionIdsByProfile.set(key, created);
|
|
157
|
+
return created;
|
|
158
|
+
}
|
|
159
|
+
function webSessionKey(profile) {
|
|
160
|
+
return profile.accountId || profile.profileId;
|
|
161
|
+
}
|
|
162
|
+
function splitSetCookieHeader(value) {
|
|
163
|
+
return value.split(/,(?=\s*[^;,=\s]+=)/).map((item) => item.trim()).filter(Boolean);
|
|
164
|
+
}
|
|
165
|
+
function storeChatGptCookies(profile, headers) {
|
|
166
|
+
const setCookie = headers["set-cookie"];
|
|
167
|
+
if (!setCookie) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const key = webSessionKey(profile);
|
|
171
|
+
const jar = cookiesByProfile.get(key) ?? /* @__PURE__ */ new Map();
|
|
172
|
+
for (const cookie of splitSetCookieHeader(setCookie)) {
|
|
173
|
+
const pair = cookie.split(";", 1)[0]?.trim();
|
|
174
|
+
const separator = pair?.indexOf("=") ?? -1;
|
|
175
|
+
if (!pair || separator <= 0) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const name = pair.slice(0, separator).trim();
|
|
179
|
+
const value = pair.slice(separator + 1);
|
|
180
|
+
if (!name) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (/;\s*max-age=0(?:\s*;|$)/i.test(cookie) || /;\s*expires=thu,\s*01 jan 1970/i.test(cookie)) {
|
|
184
|
+
jar.delete(name);
|
|
185
|
+
} else {
|
|
186
|
+
jar.set(name, value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (jar.size > 0) {
|
|
190
|
+
cookiesByProfile.set(key, jar);
|
|
191
|
+
} else {
|
|
192
|
+
cookiesByProfile.delete(key);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function getChatGptCookieHeader(profile) {
|
|
196
|
+
const jar = cookiesByProfile.get(webSessionKey(profile));
|
|
197
|
+
if (!jar || jar.size === 0) {
|
|
198
|
+
return void 0;
|
|
199
|
+
}
|
|
200
|
+
return Array.from(jar.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
201
|
+
}
|
|
202
|
+
function legacyEasternTimeParts() {
|
|
203
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
204
|
+
timeZone: "America/New_York",
|
|
205
|
+
weekday: "short",
|
|
206
|
+
month: "short",
|
|
207
|
+
day: "2-digit",
|
|
208
|
+
year: "numeric",
|
|
209
|
+
hour: "2-digit",
|
|
210
|
+
minute: "2-digit",
|
|
211
|
+
second: "2-digit",
|
|
212
|
+
hour12: false
|
|
213
|
+
}).formatToParts(/* @__PURE__ */ new Date()).reduce((acc, part) => {
|
|
214
|
+
if (part.type !== "literal") {
|
|
215
|
+
acc[part.type] = part.value;
|
|
216
|
+
}
|
|
217
|
+
return acc;
|
|
218
|
+
}, {});
|
|
219
|
+
}
|
|
220
|
+
function buildLegacyTimeString() {
|
|
221
|
+
const parts = legacyEasternTimeParts();
|
|
222
|
+
return `${parts.weekday} ${parts.month} ${parts.day} ${parts.year} ${parts.hour}:${parts.minute}:${parts.second} GMT-0500 (Eastern Standard Time)`;
|
|
223
|
+
}
|
|
224
|
+
function compactJson(value) {
|
|
225
|
+
return JSON.stringify(value);
|
|
226
|
+
}
|
|
227
|
+
function rotateLeft64(value, bits) {
|
|
228
|
+
if (bits === 0) {
|
|
229
|
+
return value & UINT64_MASK;
|
|
230
|
+
}
|
|
231
|
+
const shift = BigInt(bits);
|
|
232
|
+
return (value << shift | value >> 64n - shift) & UINT64_MASK;
|
|
233
|
+
}
|
|
234
|
+
function readUInt64LE(buffer, offset) {
|
|
235
|
+
let value = 0n;
|
|
236
|
+
for (let index = 7; index >= 0; index -= 1) {
|
|
237
|
+
value = value << 8n | BigInt(buffer[offset + index] ?? 0);
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
function writeUInt64LE(buffer, value, offset) {
|
|
242
|
+
let current = value & UINT64_MASK;
|
|
243
|
+
for (let index = 0; index < 8; index += 1) {
|
|
244
|
+
buffer[offset + index] = Number(current & 0xffn);
|
|
245
|
+
current >>= 8n;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function keccakF1600(state) {
|
|
249
|
+
const column = new Array(5).fill(0n);
|
|
250
|
+
const delta = new Array(5).fill(0n);
|
|
251
|
+
const rotated = new Array(25).fill(0n);
|
|
252
|
+
for (const roundConstant of KECCAK_ROUND_CONSTANTS) {
|
|
253
|
+
for (let x = 0; x < 5; x += 1) {
|
|
254
|
+
column[x] = (state[x] ?? 0n) ^ (state[x + 5] ?? 0n) ^ (state[x + 10] ?? 0n) ^ (state[x + 15] ?? 0n) ^ (state[x + 20] ?? 0n);
|
|
255
|
+
}
|
|
256
|
+
for (let x = 0; x < 5; x += 1) {
|
|
257
|
+
delta[x] = (column[(x + 4) % 5] ?? 0n) ^ rotateLeft64(column[(x + 1) % 5] ?? 0n, 1);
|
|
258
|
+
}
|
|
259
|
+
for (let x = 0; x < 5; x += 1) {
|
|
260
|
+
for (let y = 0; y < 5; y += 1) {
|
|
261
|
+
const index = x + 5 * y;
|
|
262
|
+
state[index] = ((state[index] ?? 0n) ^ (delta[x] ?? 0n)) & UINT64_MASK;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (let x = 0; x < 5; x += 1) {
|
|
266
|
+
for (let y = 0; y < 5; y += 1) {
|
|
267
|
+
const sourceIndex = x + 5 * y;
|
|
268
|
+
const targetIndex = y + 5 * ((2 * x + 3 * y) % 5);
|
|
269
|
+
rotated[targetIndex] = rotateLeft64(state[sourceIndex] ?? 0n, KECCAK_ROTATION_OFFSETS[x]?.[y] ?? 0);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
for (let x = 0; x < 5; x += 1) {
|
|
273
|
+
for (let y = 0; y < 5; y += 1) {
|
|
274
|
+
const index = x + 5 * y;
|
|
275
|
+
state[index] = ((rotated[index] ?? 0n) ^ ~(rotated[(x + 1) % 5 + 5 * y] ?? 0n) & UINT64_MASK & (rotated[(x + 2) % 5 + 5 * y] ?? 0n)) & UINT64_MASK;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
state[0] = ((state[0] ?? 0n) ^ roundConstant) & UINT64_MASK;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function sha3_512DigestJs(chunks) {
|
|
282
|
+
const input = Buffer.concat(chunks);
|
|
283
|
+
const remainder = input.length % SHA3_512_RATE_BYTES;
|
|
284
|
+
const paddingLength = SHA3_512_RATE_BYTES - remainder;
|
|
285
|
+
const padded = Buffer.alloc(input.length + paddingLength);
|
|
286
|
+
input.copy(padded);
|
|
287
|
+
padded[input.length] = 6;
|
|
288
|
+
padded[padded.length - 1] = (padded[padded.length - 1] ?? 0) | 128;
|
|
289
|
+
const state = new Array(25).fill(0n);
|
|
290
|
+
for (let blockOffset = 0; blockOffset < padded.length; blockOffset += SHA3_512_RATE_BYTES) {
|
|
291
|
+
for (let lane = 0; lane < SHA3_512_RATE_BYTES / 8; lane += 1) {
|
|
292
|
+
state[lane] = ((state[lane] ?? 0n) ^ readUInt64LE(padded, blockOffset + lane * 8)) & UINT64_MASK;
|
|
293
|
+
}
|
|
294
|
+
keccakF1600(state);
|
|
295
|
+
}
|
|
296
|
+
const output = Buffer.alloc(64);
|
|
297
|
+
for (let lane = 0; lane < output.length / 8; lane += 1) {
|
|
298
|
+
writeUInt64LE(output, state[lane] ?? 0n, lane * 8);
|
|
299
|
+
}
|
|
300
|
+
return output;
|
|
301
|
+
}
|
|
302
|
+
function sha3_512Digest(chunks) {
|
|
303
|
+
if (nativeSha3Supported !== false && process.env.AZT_FORCE_JS_SHA3 !== "1") {
|
|
304
|
+
try {
|
|
305
|
+
const hash = createHash("sha3-512");
|
|
306
|
+
for (const chunk of chunks) {
|
|
307
|
+
hash.update(chunk);
|
|
308
|
+
}
|
|
309
|
+
nativeSha3Supported = true;
|
|
310
|
+
return hash.digest();
|
|
311
|
+
} catch (error) {
|
|
312
|
+
nativeSha3Supported = false;
|
|
313
|
+
if (!warnedAboutSha3Fallback) {
|
|
314
|
+
warnedAboutSha3Fallback = true;
|
|
315
|
+
console.warn("[gateway:image] native sha3-512 unavailable, using JS fallback", {
|
|
316
|
+
error: error instanceof Error ? error.message : String(error)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return sha3_512DigestJs(chunks);
|
|
322
|
+
}
|
|
323
|
+
function buildPowConfig(userAgent, scriptSources, dataBuild) {
|
|
324
|
+
const nowMs = Date.now();
|
|
325
|
+
const perfMs = performance.now();
|
|
326
|
+
return [
|
|
327
|
+
randomChoice([3e3, 4e3, 5e3]),
|
|
328
|
+
buildLegacyTimeString(),
|
|
329
|
+
4294705152,
|
|
330
|
+
0,
|
|
331
|
+
userAgent,
|
|
332
|
+
randomChoice(scriptSources),
|
|
333
|
+
dataBuild,
|
|
334
|
+
"en-US",
|
|
335
|
+
"en-US,es-US,en,es",
|
|
336
|
+
0,
|
|
337
|
+
randomChoice(NAVIGATOR_KEYS),
|
|
338
|
+
randomChoice(DOCUMENT_KEYS),
|
|
339
|
+
randomChoice(WINDOW_KEYS),
|
|
340
|
+
perfMs,
|
|
341
|
+
randomUUID(),
|
|
342
|
+
"",
|
|
343
|
+
randomChoice(CORES),
|
|
344
|
+
nowMs - perfMs
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
function comparePrefix(left, right) {
|
|
348
|
+
for (let index = 0; index < right.length; index += 1) {
|
|
349
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
350
|
+
if (diff !== 0) {
|
|
351
|
+
return diff;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
function powGenerate(seed, difficulty, config, limit = 5e5) {
|
|
357
|
+
const target = Buffer.from(difficulty, "hex");
|
|
358
|
+
const diffLen = Math.floor(difficulty.length / 2);
|
|
359
|
+
const seedBytes = Buffer.from(seed);
|
|
360
|
+
const static1 = Buffer.from(`${compactJson(config.slice(0, 3)).slice(0, -1)},`);
|
|
361
|
+
const static2 = Buffer.from(`,${compactJson(config.slice(4, 9)).slice(1, -1)},`);
|
|
362
|
+
const static3 = Buffer.from(`,${compactJson(config.slice(10)).slice(1)}`);
|
|
363
|
+
for (let nonce = 0; nonce < limit; nonce += 1) {
|
|
364
|
+
const finalJson = Buffer.concat([
|
|
365
|
+
static1,
|
|
366
|
+
Buffer.from(String(nonce)),
|
|
367
|
+
static2,
|
|
368
|
+
Buffer.from(String(nonce >> 1)),
|
|
369
|
+
static3
|
|
370
|
+
]);
|
|
371
|
+
const encoded = Buffer.from(finalJson).toString("base64");
|
|
372
|
+
const digest = sha3_512Digest([seedBytes, Buffer.from(encoded)]);
|
|
373
|
+
if (comparePrefix(digest.subarray(0, diffLen), target) <= 0) {
|
|
374
|
+
return encoded;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return `wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D${Buffer.from(`"${seed}"`).toString("base64")}`;
|
|
378
|
+
}
|
|
379
|
+
function buildLegacyRequirementsToken(userAgent, scriptSources, dataBuild) {
|
|
380
|
+
const seed = String(Math.random());
|
|
381
|
+
const config = buildPowConfig(userAgent, scriptSources, dataBuild);
|
|
382
|
+
return `gAAAAAC${powGenerate(seed, "0fffff", config)}`;
|
|
383
|
+
}
|
|
384
|
+
function buildProofToken(seed, difficulty, userAgent, scriptSources, dataBuild) {
|
|
385
|
+
const config = buildPowConfig(userAgent, scriptSources, dataBuild);
|
|
386
|
+
return `gAAAAAB${powGenerate(seed, difficulty, config)}`;
|
|
387
|
+
}
|
|
388
|
+
class OrderedMap {
|
|
389
|
+
keys = [];
|
|
390
|
+
values = /* @__PURE__ */ new Map();
|
|
391
|
+
add(key, value) {
|
|
392
|
+
if (!this.values.has(key)) {
|
|
393
|
+
this.keys.push(key);
|
|
394
|
+
}
|
|
395
|
+
this.values.set(key, value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function turnstileToString(value) {
|
|
399
|
+
if (typeof value === "undefined" || value === null) {
|
|
400
|
+
return "undefined";
|
|
401
|
+
}
|
|
402
|
+
if (typeof value === "number") {
|
|
403
|
+
return String(value);
|
|
404
|
+
}
|
|
405
|
+
if (typeof value === "string") {
|
|
406
|
+
const special = {
|
|
407
|
+
"window.Math": "[object Math]",
|
|
408
|
+
"window.Reflect": "[object Reflect]",
|
|
409
|
+
"window.performance": "[object Performance]",
|
|
410
|
+
"window.localStorage": "[object Storage]",
|
|
411
|
+
"window.Object": "function Object() { [native code] }",
|
|
412
|
+
"window.Reflect.set": "function set() { [native code] }",
|
|
413
|
+
"window.performance.now": "function () { [native code] }",
|
|
414
|
+
"window.Object.create": "function create() { [native code] }",
|
|
415
|
+
"window.Object.keys": "function keys() { [native code] }",
|
|
416
|
+
"window.Math.random": "function random() { [native code] }"
|
|
417
|
+
};
|
|
418
|
+
return special[value] ?? value;
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
421
|
+
return value.join(",");
|
|
422
|
+
}
|
|
423
|
+
return String(value);
|
|
424
|
+
}
|
|
425
|
+
function xorString(text, key) {
|
|
426
|
+
if (!key) {
|
|
427
|
+
return text;
|
|
428
|
+
}
|
|
429
|
+
let result = "";
|
|
430
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
431
|
+
result += String.fromCharCode(text.charCodeAt(index) ^ key.charCodeAt(index % key.length));
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
function solveTurnstileToken(dx, p) {
|
|
436
|
+
let tokenList;
|
|
437
|
+
try {
|
|
438
|
+
const decoded = Buffer.from(dx, "base64").toString("utf8");
|
|
439
|
+
tokenList = JSON.parse(xorString(decoded, p));
|
|
440
|
+
} catch {
|
|
441
|
+
return void 0;
|
|
442
|
+
}
|
|
443
|
+
if (!Array.isArray(tokenList)) {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
const processMap = /* @__PURE__ */ new Map();
|
|
447
|
+
const startNs = process.hrtime.bigint();
|
|
448
|
+
let result = "";
|
|
449
|
+
const get = (key) => processMap.get(key);
|
|
450
|
+
const mustGet = (key) => {
|
|
451
|
+
if (!processMap.has(key)) {
|
|
452
|
+
throw new Error("turnstile process map missing key");
|
|
453
|
+
}
|
|
454
|
+
return processMap.get(key);
|
|
455
|
+
};
|
|
456
|
+
const set = (key, value) => {
|
|
457
|
+
processMap.set(key, value);
|
|
458
|
+
};
|
|
459
|
+
const func1 = (e, t) => {
|
|
460
|
+
set(e, xorString(turnstileToString(mustGet(e)), turnstileToString(mustGet(t))));
|
|
461
|
+
};
|
|
462
|
+
const func2 = (e, t) => {
|
|
463
|
+
set(e, t);
|
|
464
|
+
};
|
|
465
|
+
const func3 = (e) => {
|
|
466
|
+
result = Buffer.from(String(e)).toString("base64");
|
|
467
|
+
};
|
|
468
|
+
const func5 = (e, t) => {
|
|
469
|
+
const current = mustGet(e);
|
|
470
|
+
const incoming = mustGet(t);
|
|
471
|
+
if (Array.isArray(current)) {
|
|
472
|
+
set(e, [...current, incoming]);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (typeof current === "string" || typeof current === "number" || typeof incoming === "string" || typeof incoming === "number") {
|
|
476
|
+
set(e, turnstileToString(current) + turnstileToString(incoming));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
set(e, "NaN");
|
|
480
|
+
};
|
|
481
|
+
const func6 = (e, t, n) => {
|
|
482
|
+
const tv = mustGet(t);
|
|
483
|
+
const nv = mustGet(n);
|
|
484
|
+
if (typeof tv === "string" && typeof nv === "string") {
|
|
485
|
+
const value = `${tv}.${nv}`;
|
|
486
|
+
set(e, value === "window.document.location" ? "https://chatgpt.com/" : value);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const func7 = (e, ...args) => {
|
|
490
|
+
const target = mustGet(e);
|
|
491
|
+
const values = args.map((arg) => mustGet(arg));
|
|
492
|
+
if (target === "window.Reflect.set") {
|
|
493
|
+
const [obj, keyName, val] = values;
|
|
494
|
+
if (obj instanceof OrderedMap) {
|
|
495
|
+
obj.add(String(keyName), val);
|
|
496
|
+
}
|
|
497
|
+
} else if (typeof target === "function") {
|
|
498
|
+
target(...values);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
const func8 = (e, t) => {
|
|
502
|
+
set(e, mustGet(t));
|
|
503
|
+
};
|
|
504
|
+
const func14 = (e, t) => {
|
|
505
|
+
set(e, JSON.parse(String(mustGet(t))));
|
|
506
|
+
};
|
|
507
|
+
const func15 = (e, t) => {
|
|
508
|
+
set(e, JSON.stringify(mustGet(t)));
|
|
509
|
+
};
|
|
510
|
+
const func17 = (e, t, ...args) => {
|
|
511
|
+
const callArgs = args.map((arg) => mustGet(arg));
|
|
512
|
+
const target = mustGet(t);
|
|
513
|
+
if (target === "window.performance.now") {
|
|
514
|
+
const elapsedMs = Number(process.hrtime.bigint() - startNs) / 1e6;
|
|
515
|
+
set(e, elapsedMs + Math.random());
|
|
516
|
+
} else if (target === "window.Object.create") {
|
|
517
|
+
set(e, new OrderedMap());
|
|
518
|
+
} else if (target === "window.Object.keys") {
|
|
519
|
+
if (callArgs[0] === "window.localStorage") {
|
|
520
|
+
set(e, [
|
|
521
|
+
"STATSIG_LOCAL_STORAGE_INTERNAL_STORE_V4",
|
|
522
|
+
"STATSIG_LOCAL_STORAGE_STABLE_ID",
|
|
523
|
+
"client-correlated-secret",
|
|
524
|
+
"oai/apps/capExpiresAt",
|
|
525
|
+
"oai-did",
|
|
526
|
+
"STATSIG_LOCAL_STORAGE_LOGGING_REQUEST",
|
|
527
|
+
"UiState.isNavigationCollapsed.1"
|
|
528
|
+
]);
|
|
529
|
+
}
|
|
530
|
+
} else if (target === "window.Math.random") {
|
|
531
|
+
set(e, Math.random());
|
|
532
|
+
} else if (typeof target === "function") {
|
|
533
|
+
set(e, target(...callArgs));
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
const func18 = (e) => {
|
|
537
|
+
set(e, Buffer.from(turnstileToString(mustGet(e)), "base64").toString("utf8"));
|
|
538
|
+
};
|
|
539
|
+
const func19 = (e) => {
|
|
540
|
+
set(e, Buffer.from(turnstileToString(mustGet(e))).toString("base64"));
|
|
541
|
+
};
|
|
542
|
+
const func20 = (e, t, n, ...args) => {
|
|
543
|
+
if (mustGet(e) === mustGet(t)) {
|
|
544
|
+
const target = mustGet(n);
|
|
545
|
+
if (typeof target === "function") {
|
|
546
|
+
target(...args.map((arg) => mustGet(arg)));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
const func21 = () => void 0;
|
|
551
|
+
const func23 = (e, t, ...args) => {
|
|
552
|
+
const target = mustGet(t);
|
|
553
|
+
if (mustGet(e) !== null && typeof target === "function") {
|
|
554
|
+
target(...args);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
const func24 = (e, t, n) => {
|
|
558
|
+
const tv = mustGet(t);
|
|
559
|
+
const nv = mustGet(n);
|
|
560
|
+
if (typeof tv === "string" && typeof nv === "string") {
|
|
561
|
+
set(e, `${tv}.${nv}`);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
processMap.set(1, func1);
|
|
565
|
+
processMap.set(2, func2);
|
|
566
|
+
processMap.set(3, func3);
|
|
567
|
+
processMap.set(5, func5);
|
|
568
|
+
processMap.set(6, func6);
|
|
569
|
+
processMap.set(7, func7);
|
|
570
|
+
processMap.set(8, func8);
|
|
571
|
+
processMap.set(9, tokenList);
|
|
572
|
+
processMap.set(10, "window");
|
|
573
|
+
processMap.set(14, func14);
|
|
574
|
+
processMap.set(15, func15);
|
|
575
|
+
processMap.set(16, p);
|
|
576
|
+
processMap.set(17, func17);
|
|
577
|
+
processMap.set(18, func18);
|
|
578
|
+
processMap.set(19, func19);
|
|
579
|
+
processMap.set(20, func20);
|
|
580
|
+
processMap.set(21, func21);
|
|
581
|
+
processMap.set(23, func23);
|
|
582
|
+
processMap.set(24, func24);
|
|
583
|
+
for (const token of tokenList) {
|
|
584
|
+
if (!Array.isArray(token)) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const fn = processMap.get(token[0]);
|
|
589
|
+
if (typeof fn === "function") {
|
|
590
|
+
fn(...token.slice(1));
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return result || void 0;
|
|
596
|
+
}
|
|
597
|
+
function chatGptHeaders(profile, path, extra) {
|
|
598
|
+
const session = getWebSession(profile);
|
|
599
|
+
const headers = {
|
|
600
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
601
|
+
Origin: CHATGPT_BASE_URL,
|
|
602
|
+
Referer: `${CHATGPT_BASE_URL}/`,
|
|
603
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7",
|
|
604
|
+
"Cache-Control": "no-cache",
|
|
605
|
+
Pragma: "no-cache",
|
|
606
|
+
Priority: "u=1, i",
|
|
607
|
+
"Sec-Ch-Ua": DEFAULT_SEC_CH_UA,
|
|
608
|
+
"Sec-Ch-Ua-Arch": '"x86"',
|
|
609
|
+
"Sec-Ch-Ua-Bitness": '"64"',
|
|
610
|
+
"Sec-Ch-Ua-Full-Version": '"143.0.3650.96"',
|
|
611
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
|
612
|
+
"Sec-Ch-Ua-Model": '""',
|
|
613
|
+
"Sec-Ch-Ua-Platform": '"Windows"',
|
|
614
|
+
"Sec-Ch-Ua-Platform-Version": '"19.0.0"',
|
|
615
|
+
"Sec-Fetch-Dest": "empty",
|
|
616
|
+
"Sec-Fetch-Mode": "cors",
|
|
617
|
+
"Sec-Fetch-Site": "same-origin",
|
|
618
|
+
"OAI-Device-Id": session.deviceId,
|
|
619
|
+
"OAI-Session-Id": session.sessionId,
|
|
620
|
+
"OAI-Language": "zh-CN",
|
|
621
|
+
"OAI-Client-Version": DEFAULT_CLIENT_VERSION,
|
|
622
|
+
"OAI-Client-Build-Number": DEFAULT_CLIENT_BUILD_NUMBER,
|
|
623
|
+
"X-OpenAI-Target-Path": path,
|
|
624
|
+
"X-OpenAI-Target-Route": path,
|
|
625
|
+
Authorization: `Bearer ${profile.access}`
|
|
626
|
+
};
|
|
627
|
+
const cookie = getChatGptCookieHeader(profile);
|
|
628
|
+
if (cookie) {
|
|
629
|
+
headers.Cookie = cookie;
|
|
630
|
+
}
|
|
631
|
+
for (const [key, value] of Object.entries(extra ?? {})) {
|
|
632
|
+
if (value) {
|
|
633
|
+
headers[key] = value;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return headers;
|
|
637
|
+
}
|
|
638
|
+
function imageHeaders(profile, path, requirements, params) {
|
|
639
|
+
return chatGptHeaders(profile, path, {
|
|
640
|
+
"Content-Type": "application/json",
|
|
641
|
+
Accept: params?.accept ?? "*/*",
|
|
642
|
+
"OpenAI-Sentinel-Chat-Requirements-Token": requirements.token,
|
|
643
|
+
"OpenAI-Sentinel-Proof-Token": requirements.proofToken,
|
|
644
|
+
"OpenAI-Sentinel-Turnstile-Token": requirements.turnstileToken,
|
|
645
|
+
"OpenAI-Sentinel-SO-Token": requirements.soToken,
|
|
646
|
+
"X-Conduit-Token": params?.conduitToken,
|
|
647
|
+
"X-Oai-Turn-Trace-Id": params?.accept === "text/event-stream" ? randomUUID() : void 0
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async function requestChatGptText(params) {
|
|
651
|
+
const maxAttempts = params.maxAttempts ?? 2;
|
|
652
|
+
let lastError;
|
|
653
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
654
|
+
const startedAt = performance.now();
|
|
655
|
+
try {
|
|
656
|
+
const response = await fetch(`${CHATGPT_BASE_URL}${params.path}`, {
|
|
657
|
+
method: params.method,
|
|
658
|
+
headers: params.headers,
|
|
659
|
+
body: params.body,
|
|
660
|
+
signal: AbortSignal.timeout(params.timeoutMs)
|
|
661
|
+
});
|
|
662
|
+
const normalizedHeaders = normalizeFetchHeaders(response.headers);
|
|
663
|
+
storeChatGptCookies(params.profile, normalizedHeaders);
|
|
664
|
+
const text = await response.text();
|
|
665
|
+
console.info("[gateway:image] ChatGPT web request timing", {
|
|
666
|
+
method: params.method,
|
|
667
|
+
url: `${CHATGPT_BASE_URL}${params.path}`,
|
|
668
|
+
status: response.status,
|
|
669
|
+
transport: "fetch",
|
|
670
|
+
attempt,
|
|
671
|
+
bodyLength: text.length,
|
|
672
|
+
totalMs: roundMs(performance.now() - startedAt)
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
body: text,
|
|
676
|
+
status: response.status,
|
|
677
|
+
headers: normalizedHeaders
|
|
678
|
+
};
|
|
679
|
+
} catch (error) {
|
|
680
|
+
lastError = error;
|
|
681
|
+
console.warn("[gateway:image] ChatGPT web fetch failed", {
|
|
682
|
+
method: params.method,
|
|
683
|
+
url: `${CHATGPT_BASE_URL}${params.path}`,
|
|
684
|
+
attempt,
|
|
685
|
+
maxAttempts,
|
|
686
|
+
elapsedMs: roundMs(performance.now() - startedAt),
|
|
687
|
+
error: error instanceof Error ? error.message : String(error)
|
|
688
|
+
});
|
|
689
|
+
if (attempt < maxAttempts) {
|
|
690
|
+
await sleep(500 * attempt);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
throw new Error(`${params.path} \u8BF7\u6C42\u5931\u8D25: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
695
|
+
}
|
|
696
|
+
async function requestJson(params) {
|
|
697
|
+
const method = params.method ?? (typeof params.body === "undefined" ? "GET" : "POST");
|
|
698
|
+
const response = await requestChatGptText({
|
|
699
|
+
profile: params.profile,
|
|
700
|
+
path: params.path,
|
|
701
|
+
method,
|
|
702
|
+
headers: chatGptHeaders(params.profile, params.path, {
|
|
703
|
+
Accept: "application/json",
|
|
704
|
+
...typeof params.body === "undefined" ? {} : { "Content-Type": "application/json" },
|
|
705
|
+
...params.headers
|
|
706
|
+
}),
|
|
707
|
+
body: typeof params.body === "undefined" ? void 0 : JSON.stringify(params.body),
|
|
708
|
+
timeoutMs: params.timeoutMs ?? 6e4,
|
|
709
|
+
maxAttempts: 3
|
|
710
|
+
});
|
|
711
|
+
if (response.status < 200 || response.status >= 300) {
|
|
712
|
+
throw new Error(`${params.path} \u5931\u8D25: HTTP ${response.status} ${response.body.slice(0, 600)}`);
|
|
713
|
+
}
|
|
714
|
+
return parseJson(response.body, params.path);
|
|
715
|
+
}
|
|
716
|
+
async function bootstrap(profile) {
|
|
717
|
+
const response = await requestChatGptText({
|
|
718
|
+
profile,
|
|
719
|
+
path: "/",
|
|
720
|
+
method: "GET",
|
|
721
|
+
headers: chatGptHeaders(profile, "/", {
|
|
722
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
723
|
+
"Sec-Fetch-Dest": "document",
|
|
724
|
+
"Sec-Fetch-Mode": "navigate",
|
|
725
|
+
"Sec-Fetch-Site": "none",
|
|
726
|
+
"Sec-Fetch-User": "?1",
|
|
727
|
+
"Upgrade-Insecure-Requests": "1"
|
|
728
|
+
}),
|
|
729
|
+
timeoutMs: 3e4,
|
|
730
|
+
maxAttempts: 2
|
|
731
|
+
});
|
|
732
|
+
if (response.status < 200 || response.status >= 300) {
|
|
733
|
+
throw new Error(`ChatGPT \u5B98\u7F51\u9884\u70ED\u5931\u8D25: HTTP ${response.status} ${response.body.slice(0, 600)}`);
|
|
734
|
+
}
|
|
735
|
+
return parsePowResources(response.body);
|
|
736
|
+
}
|
|
737
|
+
async function getChatRequirements(profile, resources) {
|
|
738
|
+
const path = "/backend-api/sentinel/chat-requirements";
|
|
739
|
+
const p = buildLegacyRequirementsToken(DEFAULT_USER_AGENT, resources.scriptSources, resources.dataBuild);
|
|
740
|
+
const response = await requestJson({
|
|
741
|
+
profile,
|
|
742
|
+
path,
|
|
743
|
+
body: { p },
|
|
744
|
+
timeoutMs: 3e4
|
|
745
|
+
});
|
|
746
|
+
const data = isRecord(response) ? response : {};
|
|
747
|
+
const arkose = isRecord(data.arkose) ? data.arkose : {};
|
|
748
|
+
if (arkose.required) {
|
|
749
|
+
throw new Error("ChatGPT \u5B98\u7F51\u56FE\u7247\u94FE\u8DEF\u8981\u6C42 Arkose \u9A8C\u8BC1\uFF0C\u5F53\u524D\u7F51\u5173\u65E0\u6CD5\u81EA\u52A8\u5B8C\u6210\u3002");
|
|
750
|
+
}
|
|
751
|
+
const proofInfo = isRecord(data.proofofwork) ? data.proofofwork : {};
|
|
752
|
+
const proofToken = proofInfo.required && typeof proofInfo.seed === "string" && typeof proofInfo.difficulty === "string" ? buildProofToken(proofInfo.seed, proofInfo.difficulty, DEFAULT_USER_AGENT, resources.scriptSources, resources.dataBuild) : void 0;
|
|
753
|
+
const turnstile = isRecord(data.turnstile) ? data.turnstile : {};
|
|
754
|
+
const turnstileToken = turnstile.required && typeof turnstile.dx === "string" ? solveTurnstileToken(turnstile.dx, p) : void 0;
|
|
755
|
+
const token = typeof data.token === "string" ? data.token : "";
|
|
756
|
+
if (!token) {
|
|
757
|
+
throw new Error(`ChatGPT sentinel \u54CD\u5E94\u7F3A\u5C11 token: ${JSON.stringify(data).slice(0, 600)}`);
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
token,
|
|
761
|
+
proofToken,
|
|
762
|
+
turnstileToken,
|
|
763
|
+
soToken: typeof data.so_token === "string" ? data.so_token : void 0
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function imageModelSlug(model) {
|
|
767
|
+
const value = String(model ?? "").trim();
|
|
768
|
+
if (!value) {
|
|
769
|
+
return "auto";
|
|
770
|
+
}
|
|
771
|
+
if (value === "gpt-image-2") {
|
|
772
|
+
return "gpt-5-3";
|
|
773
|
+
}
|
|
774
|
+
if (value === CODEX_IMAGE_MODEL) {
|
|
775
|
+
return value;
|
|
776
|
+
}
|
|
777
|
+
return "auto";
|
|
778
|
+
}
|
|
779
|
+
function promptWithSize(prompt, size) {
|
|
780
|
+
if (!size) {
|
|
781
|
+
return prompt;
|
|
782
|
+
}
|
|
783
|
+
const hints = {
|
|
784
|
+
"1:1": "\u8F93\u51FA\u4E3A 1:1 \u6B63\u65B9\u5F62\u6784\u56FE\uFF0C\u4E3B\u4F53\u5C45\u4E2D\uFF0C\u9002\u5408\u6B63\u65B9\u5F62\u753B\u5E45\u3002",
|
|
785
|
+
"16:9": "\u8F93\u51FA\u4E3A 16:9 \u6A2A\u5C4F\u6784\u56FE\uFF0C\u9002\u5408\u5BBD\u753B\u5E45\u5C55\u793A\u3002",
|
|
786
|
+
"9:16": "\u8F93\u51FA\u4E3A 9:16 \u7AD6\u5C4F\u6784\u56FE\uFF0C\u9002\u5408\u7AD6\u7248\u753B\u5E45\u3002",
|
|
787
|
+
"4:3": "\u8F93\u51FA\u4E3A 4:3 \u6BD4\u4F8B\uFF0C\u517C\u987E\u5BBD\u5EA6\u4E0E\u9AD8\u5EA6\uFF0C\u9002\u5408\u5C55\u793A\u753B\u9762\u7EC6\u8282\u3002",
|
|
788
|
+
"3:4": "\u8F93\u51FA\u4E3A 3:4 \u6BD4\u4F8B\uFF0C\u7EB5\u5411\u6784\u56FE\uFF0C\u9002\u5408\u4EBA\u7269\u8096\u50CF\u6216\u7AD6\u5411\u573A\u666F\u3002"
|
|
789
|
+
};
|
|
790
|
+
const normalized = size.trim();
|
|
791
|
+
const hint = hints[normalized] ?? `\u8F93\u51FA\u56FE\u7247\uFF0C\u5BBD\u9AD8\u6BD4\u4E3A ${normalized}\u3002`;
|
|
792
|
+
return `${prompt.trim()}
|
|
793
|
+
|
|
794
|
+
${hint}`;
|
|
795
|
+
}
|
|
796
|
+
function toChatGptSize(size) {
|
|
797
|
+
if (!size) {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
const value = size.trim();
|
|
801
|
+
if (value === "1024x1024") {
|
|
802
|
+
return "1:1";
|
|
803
|
+
}
|
|
804
|
+
if (value === "1536x1024" || value === "1792x1024") {
|
|
805
|
+
return "16:9";
|
|
806
|
+
}
|
|
807
|
+
if (value === "1024x1536" || value === "1024x1792") {
|
|
808
|
+
return "9:16";
|
|
809
|
+
}
|
|
810
|
+
return value;
|
|
811
|
+
}
|
|
812
|
+
async function prepareImageConversation(params) {
|
|
813
|
+
const path = "/backend-api/f/conversation/prepare";
|
|
814
|
+
const response = await requestChatGptText({
|
|
815
|
+
profile: params.profile,
|
|
816
|
+
path,
|
|
817
|
+
method: "POST",
|
|
818
|
+
headers: imageHeaders(params.profile, path, params.requirements),
|
|
819
|
+
body: JSON.stringify({
|
|
820
|
+
action: "next",
|
|
821
|
+
fork_from_shared_post: false,
|
|
822
|
+
parent_message_id: randomUUID(),
|
|
823
|
+
model: imageModelSlug(params.model),
|
|
824
|
+
client_prepare_state: "success",
|
|
825
|
+
timezone_offset_min: -480,
|
|
826
|
+
timezone: "Asia/Shanghai",
|
|
827
|
+
conversation_mode: { kind: "primary_assistant" },
|
|
828
|
+
system_hints: ["picture_v2"],
|
|
829
|
+
partial_query: {
|
|
830
|
+
id: randomUUID(),
|
|
831
|
+
author: { role: "user" },
|
|
832
|
+
content: { content_type: "text", parts: [params.prompt] }
|
|
833
|
+
},
|
|
834
|
+
supports_buffering: true,
|
|
835
|
+
supported_encodings: ["v1"],
|
|
836
|
+
client_contextual_info: { app_name: "chatgpt.com" }
|
|
837
|
+
}),
|
|
838
|
+
timeoutMs: 6e4,
|
|
839
|
+
maxAttempts: 2
|
|
840
|
+
});
|
|
841
|
+
if (response.status < 200 || response.status >= 300) {
|
|
842
|
+
throw new Error(`${path} \u5931\u8D25: HTTP ${response.status} ${response.body.slice(0, 600)}`);
|
|
843
|
+
}
|
|
844
|
+
const data = parseJson(response.body, path);
|
|
845
|
+
if (!isRecord(data) || typeof data.conduit_token !== "string" || !data.conduit_token) {
|
|
846
|
+
throw new Error(`${path} \u54CD\u5E94\u7F3A\u5C11 conduit_token\u3002`);
|
|
847
|
+
}
|
|
848
|
+
return data.conduit_token;
|
|
849
|
+
}
|
|
850
|
+
function parseDataUrl(value) {
|
|
851
|
+
const match = /^data:([^;,]+);base64,(.+)$/is.exec(value.trim());
|
|
852
|
+
if (!match) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
mimeType: match[1] || "image/png",
|
|
857
|
+
bytes: Buffer.from(match[2] ?? "", "base64")
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
async function fetchImageReference(imageUrl) {
|
|
861
|
+
const parsed = parseDataUrl(imageUrl);
|
|
862
|
+
if (parsed) {
|
|
863
|
+
return {
|
|
864
|
+
bytes: parsed.bytes,
|
|
865
|
+
mimeType: parsed.mimeType,
|
|
866
|
+
fileName: `image.${extensionFromMime(parsed.mimeType)}`
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (!/^https?:\/\//i.test(imageUrl)) {
|
|
870
|
+
const raw = imageUrl.includes(",") ? imageUrl.split(",", 2)[1] ?? "" : imageUrl;
|
|
871
|
+
return {
|
|
872
|
+
bytes: Buffer.from(raw, "base64"),
|
|
873
|
+
mimeType: "image/png",
|
|
874
|
+
fileName: "image.png"
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
const response = await fetch(imageUrl, {
|
|
878
|
+
headers: {
|
|
879
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
880
|
+
Accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
if (!response.ok) {
|
|
884
|
+
throw new Error(`\u4E0B\u8F7D\u53C2\u8003\u56FE\u5931\u8D25: HTTP ${response.status}`);
|
|
885
|
+
}
|
|
886
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
|
|
887
|
+
const urlPath = new URL(imageUrl).pathname;
|
|
888
|
+
const lastName = urlPath.split("/").filter(Boolean).pop();
|
|
889
|
+
return {
|
|
890
|
+
bytes: Buffer.from(await response.arrayBuffer()),
|
|
891
|
+
mimeType: contentType,
|
|
892
|
+
fileName: lastName || `image.${extensionFromMime(contentType)}`
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function extensionFromMime(mimeType) {
|
|
896
|
+
const subtype = mimeType.split("/", 2)[1]?.split("+", 1)[0]?.toLowerCase() || "png";
|
|
897
|
+
return subtype === "jpeg" ? "jpg" : subtype;
|
|
898
|
+
}
|
|
899
|
+
function imageDimensions(bytes) {
|
|
900
|
+
if (bytes.length >= 24 && bytes.toString("ascii", 1, 4) === "PNG") {
|
|
901
|
+
return {
|
|
902
|
+
width: bytes.readUInt32BE(16),
|
|
903
|
+
height: bytes.readUInt32BE(20)
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216) {
|
|
907
|
+
let offset = 2;
|
|
908
|
+
while (offset + 9 < bytes.length) {
|
|
909
|
+
if (bytes[offset] !== 255) {
|
|
910
|
+
offset += 1;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const marker = bytes[offset + 1];
|
|
914
|
+
const length = bytes.readUInt16BE(offset + 2);
|
|
915
|
+
if (length < 2) {
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
if (marker !== void 0 && (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207)) {
|
|
919
|
+
return {
|
|
920
|
+
height: bytes.readUInt16BE(offset + 5),
|
|
921
|
+
width: bytes.readUInt16BE(offset + 7)
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
offset += 2 + length;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return { width: 1024, height: 1024 };
|
|
928
|
+
}
|
|
929
|
+
async function uploadImage(profile, imageUrl, index) {
|
|
930
|
+
const image = await fetchImageReference(imageUrl);
|
|
931
|
+
const dimensions = imageDimensions(image.bytes);
|
|
932
|
+
const fileName = image.fileName || `image_${index}.${extensionFromMime(image.mimeType)}`;
|
|
933
|
+
const createPath = "/backend-api/files";
|
|
934
|
+
const metadata = await requestJson({
|
|
935
|
+
profile,
|
|
936
|
+
path: createPath,
|
|
937
|
+
body: {
|
|
938
|
+
file_name: fileName,
|
|
939
|
+
file_size: image.bytes.length,
|
|
940
|
+
use_case: "multimodal",
|
|
941
|
+
width: dimensions.width,
|
|
942
|
+
height: dimensions.height
|
|
943
|
+
},
|
|
944
|
+
timeoutMs: 6e4
|
|
945
|
+
});
|
|
946
|
+
if (!isRecord(metadata) || typeof metadata.upload_url !== "string" || typeof metadata.file_id !== "string") {
|
|
947
|
+
throw new Error("ChatGPT \u6587\u4EF6\u4E0A\u4F20\u521D\u59CB\u5316\u54CD\u5E94\u7F3A\u5C11 upload_url \u6216 file_id\u3002");
|
|
948
|
+
}
|
|
949
|
+
const uploadResponse = await fetch(metadata.upload_url, {
|
|
950
|
+
method: "PUT",
|
|
951
|
+
headers: {
|
|
952
|
+
"Content-Type": image.mimeType,
|
|
953
|
+
"x-ms-blob-type": "BlockBlob",
|
|
954
|
+
"x-ms-version": "2020-04-08",
|
|
955
|
+
Origin: CHATGPT_BASE_URL,
|
|
956
|
+
Referer: `${CHATGPT_BASE_URL}/`,
|
|
957
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
958
|
+
Accept: "application/json, text/plain, */*"
|
|
959
|
+
},
|
|
960
|
+
body: new Uint8Array(image.bytes)
|
|
961
|
+
});
|
|
962
|
+
if (!uploadResponse.ok) {
|
|
963
|
+
throw new Error(`\u4E0A\u4F20\u53C2\u8003\u56FE\u5931\u8D25: HTTP ${uploadResponse.status} ${await uploadResponse.text().catch(() => "")}`);
|
|
964
|
+
}
|
|
965
|
+
const uploadedPath = `/backend-api/files/${metadata.file_id}/uploaded`;
|
|
966
|
+
await requestJson({
|
|
967
|
+
profile,
|
|
968
|
+
path: uploadedPath,
|
|
969
|
+
body: {},
|
|
970
|
+
timeoutMs: 6e4
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
fileId: metadata.file_id,
|
|
974
|
+
fileName,
|
|
975
|
+
fileSize: image.bytes.length,
|
|
976
|
+
mimeType: image.mimeType,
|
|
977
|
+
width: dimensions.width,
|
|
978
|
+
height: dimensions.height
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function attachmentParts(references) {
|
|
982
|
+
return references.map((item) => ({
|
|
983
|
+
content_type: "image_asset_pointer",
|
|
984
|
+
asset_pointer: `file-service://${item.fileId}`,
|
|
985
|
+
width: item.width,
|
|
986
|
+
height: item.height,
|
|
987
|
+
size_bytes: item.fileSize
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
function attachmentMetadata(references) {
|
|
991
|
+
return references.map((item) => ({
|
|
992
|
+
id: item.fileId,
|
|
993
|
+
mimeType: item.mimeType,
|
|
994
|
+
name: item.fileName,
|
|
995
|
+
size: item.fileSize,
|
|
996
|
+
width: item.width,
|
|
997
|
+
height: item.height
|
|
998
|
+
}));
|
|
999
|
+
}
|
|
1000
|
+
async function* ssePayloads(body) {
|
|
1001
|
+
const reader = body.getReader();
|
|
1002
|
+
const decoder = new TextDecoder();
|
|
1003
|
+
let buffer = "";
|
|
1004
|
+
try {
|
|
1005
|
+
while (true) {
|
|
1006
|
+
const { done, value } = await reader.read();
|
|
1007
|
+
if (done) {
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1011
|
+
let separator = buffer.indexOf("\n\n");
|
|
1012
|
+
while (separator !== -1) {
|
|
1013
|
+
const block = buffer.slice(0, separator);
|
|
1014
|
+
buffer = buffer.slice(separator + 2);
|
|
1015
|
+
const payload = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).trim()).join("\n");
|
|
1016
|
+
if (payload) {
|
|
1017
|
+
yield payload;
|
|
1018
|
+
}
|
|
1019
|
+
separator = buffer.indexOf("\n\n");
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
} finally {
|
|
1023
|
+
reader.releaseLock();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
async function readStreamText(body, maxLength = 2e3) {
|
|
1027
|
+
const reader = body.getReader();
|
|
1028
|
+
const decoder = new TextDecoder();
|
|
1029
|
+
let text = "";
|
|
1030
|
+
try {
|
|
1031
|
+
while (text.length < maxLength) {
|
|
1032
|
+
const { done, value } = await reader.read();
|
|
1033
|
+
if (done) {
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
text += decoder.decode(value, { stream: true });
|
|
1037
|
+
}
|
|
1038
|
+
text += decoder.decode();
|
|
1039
|
+
if (text.length > maxLength) {
|
|
1040
|
+
return text.slice(0, maxLength);
|
|
1041
|
+
}
|
|
1042
|
+
return text;
|
|
1043
|
+
} finally {
|
|
1044
|
+
await reader.cancel().catch(() => void 0);
|
|
1045
|
+
reader.releaseLock();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async function requestChatGptStream(params) {
|
|
1049
|
+
const startedAt = performance.now();
|
|
1050
|
+
const response = await fetch(`${CHATGPT_BASE_URL}${params.path}`, {
|
|
1051
|
+
method: "POST",
|
|
1052
|
+
headers: params.headers,
|
|
1053
|
+
body: params.body,
|
|
1054
|
+
signal: AbortSignal.timeout(params.timeoutMs)
|
|
1055
|
+
});
|
|
1056
|
+
const normalizedHeaders = normalizeFetchHeaders(response.headers);
|
|
1057
|
+
storeChatGptCookies(params.profile, normalizedHeaders);
|
|
1058
|
+
console.info("[gateway:image] ChatGPT web stream timing", {
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
url: `${CHATGPT_BASE_URL}${params.path}`,
|
|
1061
|
+
status: response.status,
|
|
1062
|
+
transport: "fetch",
|
|
1063
|
+
waitForHeadersMs: roundMs(performance.now() - startedAt)
|
|
1064
|
+
});
|
|
1065
|
+
if (!response.body) {
|
|
1066
|
+
throw new Error(`${params.path} \u54CD\u5E94\u7F3A\u5C11 body\u3002`);
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
status: response.status,
|
|
1070
|
+
headers: normalizedHeaders,
|
|
1071
|
+
body: response.body
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function addUnique(target, values) {
|
|
1075
|
+
for (const value of values) {
|
|
1076
|
+
if (value && !target.includes(value)) {
|
|
1077
|
+
target.push(value);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function extractIdsFromText(text) {
|
|
1082
|
+
return {
|
|
1083
|
+
conversationId: /"conversation_id"\s*:\s*"([^"]+)"/.exec(text)?.[1],
|
|
1084
|
+
fileIds: Array.from(text.matchAll(/(file[-_][A-Za-z0-9]+)/g)).map((match) => match[1]),
|
|
1085
|
+
sedimentIds: Array.from(text.matchAll(/sediment:\/\/([A-Za-z0-9_-]+)/g)).map((match) => match[1])
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function assistantText(event) {
|
|
1089
|
+
for (const candidate of [event, isRecord(event.v) ? event.v : null]) {
|
|
1090
|
+
if (!isRecord(candidate) || !isRecord(candidate.message)) {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const message = candidate.message;
|
|
1094
|
+
const author = isRecord(message.author) ? message.author : {};
|
|
1095
|
+
const content = isRecord(message.content) ? message.content : {};
|
|
1096
|
+
if (author.role !== "assistant" || !Array.isArray(content.parts)) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
return content.parts.filter((part) => typeof part === "string").join("");
|
|
1100
|
+
}
|
|
1101
|
+
return "";
|
|
1102
|
+
}
|
|
1103
|
+
function updateState(state, payload, event) {
|
|
1104
|
+
const ids = extractIdsFromText(payload);
|
|
1105
|
+
if (ids.conversationId && !state.conversationId) {
|
|
1106
|
+
state.conversationId = ids.conversationId;
|
|
1107
|
+
}
|
|
1108
|
+
if (event) {
|
|
1109
|
+
if (typeof event.conversation_id === "string") {
|
|
1110
|
+
state.conversationId = event.conversation_id;
|
|
1111
|
+
}
|
|
1112
|
+
const value = isRecord(event.v) ? event.v : null;
|
|
1113
|
+
if (value && typeof value.conversation_id === "string") {
|
|
1114
|
+
state.conversationId = value.conversation_id;
|
|
1115
|
+
}
|
|
1116
|
+
if (event.type === "moderation" && isRecord(event.moderation_response) && event.moderation_response.blocked === true) {
|
|
1117
|
+
state.blocked = true;
|
|
1118
|
+
}
|
|
1119
|
+
if (event.type === "server_ste_metadata" && isRecord(event.metadata)) {
|
|
1120
|
+
if (typeof event.metadata.tool_invoked === "boolean") {
|
|
1121
|
+
state.toolInvoked = event.metadata.tool_invoked;
|
|
1122
|
+
}
|
|
1123
|
+
if (typeof event.metadata.turn_use_case === "string") {
|
|
1124
|
+
state.turnUseCase = event.metadata.turn_use_case;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const message = isRecord(event.message) ? event.message : isRecord(event.v) && isRecord(event.v.message) ? event.v.message : null;
|
|
1128
|
+
const metadata = message && isRecord(message.metadata) ? message.metadata : {};
|
|
1129
|
+
const author = message && isRecord(message.author) ? message.author : {};
|
|
1130
|
+
if (author.role === "tool" && metadata.async_task_type === "image_gen") {
|
|
1131
|
+
addUnique(state.fileIds, ids.fileIds);
|
|
1132
|
+
addUnique(state.sedimentIds, ids.sedimentIds);
|
|
1133
|
+
}
|
|
1134
|
+
const nextText = assistantText(event);
|
|
1135
|
+
if (nextText) {
|
|
1136
|
+
state.text = nextText;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
async function runImageConversation(params) {
|
|
1141
|
+
const path = "/backend-api/f/conversation";
|
|
1142
|
+
const parts = attachmentParts(params.references);
|
|
1143
|
+
parts.push(params.prompt);
|
|
1144
|
+
const metadata = {
|
|
1145
|
+
developer_mode_connector_ids: [],
|
|
1146
|
+
selected_github_repos: [],
|
|
1147
|
+
selected_all_github_repos: false,
|
|
1148
|
+
system_hints: ["picture_v2"],
|
|
1149
|
+
serialization_metadata: { custom_symbol_offsets: [] }
|
|
1150
|
+
};
|
|
1151
|
+
if (params.references.length > 0) {
|
|
1152
|
+
metadata.attachments = attachmentMetadata(params.references);
|
|
1153
|
+
}
|
|
1154
|
+
const response = await requestChatGptStream({
|
|
1155
|
+
profile: params.profile,
|
|
1156
|
+
path,
|
|
1157
|
+
headers: imageHeaders(params.profile, path, params.requirements, {
|
|
1158
|
+
conduitToken: params.conduitToken,
|
|
1159
|
+
accept: "text/event-stream"
|
|
1160
|
+
}),
|
|
1161
|
+
body: JSON.stringify({
|
|
1162
|
+
action: "next",
|
|
1163
|
+
messages: [
|
|
1164
|
+
{
|
|
1165
|
+
id: randomUUID(),
|
|
1166
|
+
author: { role: "user" },
|
|
1167
|
+
create_time: Date.now() / 1e3,
|
|
1168
|
+
content: params.references.length > 0 ? { content_type: "multimodal_text", parts } : { content_type: "text", parts: [params.prompt] },
|
|
1169
|
+
metadata
|
|
1170
|
+
}
|
|
1171
|
+
],
|
|
1172
|
+
parent_message_id: randomUUID(),
|
|
1173
|
+
model: imageModelSlug(params.model),
|
|
1174
|
+
client_prepare_state: "sent",
|
|
1175
|
+
timezone_offset_min: -480,
|
|
1176
|
+
timezone: "Asia/Shanghai",
|
|
1177
|
+
conversation_mode: { kind: "primary_assistant" },
|
|
1178
|
+
enable_message_followups: true,
|
|
1179
|
+
system_hints: ["picture_v2"],
|
|
1180
|
+
supports_buffering: true,
|
|
1181
|
+
supported_encodings: ["v1"],
|
|
1182
|
+
client_contextual_info: {
|
|
1183
|
+
is_dark_mode: false,
|
|
1184
|
+
time_since_loaded: 1200,
|
|
1185
|
+
page_height: 1072,
|
|
1186
|
+
page_width: 1724,
|
|
1187
|
+
pixel_ratio: 1.2,
|
|
1188
|
+
screen_height: 1440,
|
|
1189
|
+
screen_width: 2560,
|
|
1190
|
+
app_name: "chatgpt.com"
|
|
1191
|
+
},
|
|
1192
|
+
paragen_cot_summary_display_override: "allow",
|
|
1193
|
+
force_parallel_switch: "auto"
|
|
1194
|
+
}),
|
|
1195
|
+
timeoutMs: 3e5
|
|
1196
|
+
});
|
|
1197
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1198
|
+
const body = await readStreamText(response.body).catch(() => "");
|
|
1199
|
+
throw new Error(`${path} \u5931\u8D25: HTTP ${response.status}${body ? ` ${body}` : ""}`);
|
|
1200
|
+
}
|
|
1201
|
+
const state = {
|
|
1202
|
+
text: "",
|
|
1203
|
+
conversationId: "",
|
|
1204
|
+
fileIds: [],
|
|
1205
|
+
sedimentIds: [],
|
|
1206
|
+
blocked: false,
|
|
1207
|
+
turnUseCase: ""
|
|
1208
|
+
};
|
|
1209
|
+
for await (const payload of ssePayloads(response.body)) {
|
|
1210
|
+
if (payload === "[DONE]") {
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
try {
|
|
1214
|
+
const parsed = JSON.parse(payload);
|
|
1215
|
+
updateState(state, payload, isRecord(parsed) ? parsed : void 0);
|
|
1216
|
+
} catch {
|
|
1217
|
+
updateState(state, payload);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return state;
|
|
1221
|
+
}
|
|
1222
|
+
function extractImageToolRecords(conversation) {
|
|
1223
|
+
const mapping = isRecord(conversation) && isRecord(conversation.mapping) ? conversation.mapping : {};
|
|
1224
|
+
const fileIds = [];
|
|
1225
|
+
const sedimentIds = [];
|
|
1226
|
+
for (const node of Object.values(mapping)) {
|
|
1227
|
+
const message = isRecord(node) && isRecord(node.message) ? node.message : null;
|
|
1228
|
+
const author = message && isRecord(message.author) ? message.author : {};
|
|
1229
|
+
const metadata = message && isRecord(message.metadata) ? message.metadata : {};
|
|
1230
|
+
const content = message && isRecord(message.content) ? message.content : {};
|
|
1231
|
+
if (author.role !== "tool" || metadata.async_task_type !== "image_gen" || !Array.isArray(content.parts)) {
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
for (const part of content.parts) {
|
|
1235
|
+
const text = isRecord(part) && typeof part.asset_pointer === "string" ? part.asset_pointer : typeof part === "string" ? part : "";
|
|
1236
|
+
addUnique(fileIds, Array.from(text.matchAll(/file-service:\/\/([A-Za-z0-9_-]+)/g)).map((match) => match[1]));
|
|
1237
|
+
addUnique(sedimentIds, Array.from(text.matchAll(/sediment:\/\/([A-Za-z0-9_-]+)/g)).map((match) => match[1]));
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return { fileIds, sedimentIds };
|
|
1241
|
+
}
|
|
1242
|
+
async function pollImageIds(profile, conversationId) {
|
|
1243
|
+
const startedAt = Date.now();
|
|
1244
|
+
while (Date.now() - startedAt < IMAGE_POLL_TIMEOUT_MS) {
|
|
1245
|
+
const conversation = await requestJson({
|
|
1246
|
+
profile,
|
|
1247
|
+
path: `/backend-api/conversation/${conversationId}`,
|
|
1248
|
+
method: "GET",
|
|
1249
|
+
timeoutMs: 6e4
|
|
1250
|
+
});
|
|
1251
|
+
const ids = extractImageToolRecords(conversation);
|
|
1252
|
+
if (ids.fileIds.length > 0 || ids.sedimentIds.length > 0) {
|
|
1253
|
+
return ids;
|
|
1254
|
+
}
|
|
1255
|
+
await sleep(IMAGE_POLL_INTERVAL_MS);
|
|
1256
|
+
}
|
|
1257
|
+
return { fileIds: [], sedimentIds: [] };
|
|
1258
|
+
}
|
|
1259
|
+
async function resolveDownloadUrl(profile, conversationId, id, source) {
|
|
1260
|
+
const path = source === "file" ? `/backend-api/files/${id}/download` : `/backend-api/conversation/${conversationId}/attachment/${id}/download`;
|
|
1261
|
+
const response = await requestJson({
|
|
1262
|
+
profile,
|
|
1263
|
+
path,
|
|
1264
|
+
method: "GET",
|
|
1265
|
+
timeoutMs: 6e4
|
|
1266
|
+
});
|
|
1267
|
+
if (!isRecord(response)) {
|
|
1268
|
+
return "";
|
|
1269
|
+
}
|
|
1270
|
+
return typeof response.download_url === "string" ? response.download_url : typeof response.url === "string" ? response.url : "";
|
|
1271
|
+
}
|
|
1272
|
+
function imageAcceptHeader() {
|
|
1273
|
+
return "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
|
|
1274
|
+
}
|
|
1275
|
+
function isChatGptUrl(url) {
|
|
1276
|
+
return url.hostname === "chatgpt.com" || url.hostname.endsWith(".chatgpt.com");
|
|
1277
|
+
}
|
|
1278
|
+
async function fetchImageBytes(url, headers) {
|
|
1279
|
+
let response;
|
|
1280
|
+
try {
|
|
1281
|
+
response = await fetch(url, {
|
|
1282
|
+
headers,
|
|
1283
|
+
signal: AbortSignal.timeout(6e4)
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
throw new Error(`\u8BF7\u6C42\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
1287
|
+
}
|
|
1288
|
+
if (!response.ok) {
|
|
1289
|
+
const body = await response.text().catch(() => "");
|
|
1290
|
+
throw new Error(`HTTP ${response.status}${body ? ` ${body.slice(0, 300)}` : ""}`);
|
|
1291
|
+
}
|
|
1292
|
+
return Buffer.from(await response.arrayBuffer());
|
|
1293
|
+
}
|
|
1294
|
+
async function downloadImage(profile, url) {
|
|
1295
|
+
const parsed = new URL(url);
|
|
1296
|
+
const path = `${parsed.pathname}${parsed.search}`;
|
|
1297
|
+
const browserImageHeaders = {
|
|
1298
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
1299
|
+
Accept: imageAcceptHeader(),
|
|
1300
|
+
Referer: `${CHATGPT_BASE_URL}/`
|
|
1301
|
+
};
|
|
1302
|
+
const authenticatedHeaders = chatGptHeaders(profile, path, {
|
|
1303
|
+
Accept: imageAcceptHeader()
|
|
1304
|
+
});
|
|
1305
|
+
const headerCandidates = isChatGptUrl(parsed) ? [authenticatedHeaders, browserImageHeaders] : [browserImageHeaders, authenticatedHeaders];
|
|
1306
|
+
const errors = [];
|
|
1307
|
+
for (const headers of headerCandidates) {
|
|
1308
|
+
try {
|
|
1309
|
+
return await fetchImageBytes(url, headers);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
throw new Error(`\u4E0B\u8F7D\u751F\u6210\u56FE\u7247\u5931\u8D25: ${errors.join("\uFF1B")}`);
|
|
1315
|
+
}
|
|
1316
|
+
async function resolveImages(profile, state) {
|
|
1317
|
+
if (!state.conversationId) {
|
|
1318
|
+
throw new Error("ChatGPT \u5B98\u7F51\u56FE\u7247\u94FE\u8DEF\u6CA1\u6709\u8FD4\u56DE conversation_id\u3002");
|
|
1319
|
+
}
|
|
1320
|
+
let fileIds = state.fileIds.filter((id) => id !== "file_upload");
|
|
1321
|
+
let sedimentIds = state.sedimentIds;
|
|
1322
|
+
if (fileIds.length === 0 && sedimentIds.length === 0) {
|
|
1323
|
+
const polled = await pollImageIds(profile, state.conversationId);
|
|
1324
|
+
fileIds = polled.fileIds.filter((id) => id !== "file_upload");
|
|
1325
|
+
sedimentIds = polled.sedimentIds;
|
|
1326
|
+
}
|
|
1327
|
+
const urls = [];
|
|
1328
|
+
for (const fileId of fileIds) {
|
|
1329
|
+
const url = await resolveDownloadUrl(profile, state.conversationId, fileId, "file").catch(() => "");
|
|
1330
|
+
if (url) {
|
|
1331
|
+
urls.push(url);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (urls.length === 0) {
|
|
1335
|
+
for (const sedimentId of sedimentIds) {
|
|
1336
|
+
const url = await resolveDownloadUrl(profile, state.conversationId, sedimentId, "sediment").catch(() => "");
|
|
1337
|
+
if (url) {
|
|
1338
|
+
urls.push(url);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (urls.length === 0) {
|
|
1343
|
+
const reason = state.text || "\u6CA1\u6709\u89E3\u6790\u51FA\u751F\u6210\u56FE\u7247\u6587\u4EF6\u3002";
|
|
1344
|
+
throw new Error(`ChatGPT \u5B98\u7F51\u56FE\u7247\u751F\u6210\u5931\u8D25: ${reason}`);
|
|
1345
|
+
}
|
|
1346
|
+
const images = [];
|
|
1347
|
+
const errors = [];
|
|
1348
|
+
for (const url of urls) {
|
|
1349
|
+
try {
|
|
1350
|
+
images.push(await downloadImage(profile, url));
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (images.length > 0) {
|
|
1356
|
+
if (errors.length > 0) {
|
|
1357
|
+
console.warn("[gateway:image] skipped failed generated image downloads", {
|
|
1358
|
+
failed: errors.length,
|
|
1359
|
+
total: urls.length,
|
|
1360
|
+
firstError: errors[0]
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
return images;
|
|
1364
|
+
}
|
|
1365
|
+
throw new Error(`ChatGPT \u5B98\u7F51\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25: ${errors.join("\uFF1B")}`);
|
|
1366
|
+
}
|
|
1367
|
+
async function generateChatGPTWebImage(request) {
|
|
1368
|
+
const chatGptSize = toChatGptSize(request.size);
|
|
1369
|
+
const prompt = promptWithSize(request.prompt, chatGptSize);
|
|
1370
|
+
const resources = await bootstrap(request.profile);
|
|
1371
|
+
const requirements = await getChatRequirements(request.profile, resources);
|
|
1372
|
+
const references = await Promise.all(
|
|
1373
|
+
(request.inputImages ?? []).map((image, index) => uploadImage(request.profile, image.imageUrl, index + 1))
|
|
1374
|
+
);
|
|
1375
|
+
const conduitToken = await prepareImageConversation({
|
|
1376
|
+
profile: request.profile,
|
|
1377
|
+
requirements,
|
|
1378
|
+
prompt,
|
|
1379
|
+
model: request.model
|
|
1380
|
+
});
|
|
1381
|
+
const state = await runImageConversation({
|
|
1382
|
+
profile: request.profile,
|
|
1383
|
+
requirements,
|
|
1384
|
+
conduitToken,
|
|
1385
|
+
prompt,
|
|
1386
|
+
model: request.model,
|
|
1387
|
+
references
|
|
1388
|
+
});
|
|
1389
|
+
const images = await resolveImages(request.profile, state);
|
|
1390
|
+
return {
|
|
1391
|
+
created: unixSeconds(),
|
|
1392
|
+
data: images.map((image) => ({
|
|
1393
|
+
b64_json: image.toString("base64"),
|
|
1394
|
+
revised_prompt: request.prompt
|
|
1395
|
+
})),
|
|
1396
|
+
output_format: "png",
|
|
1397
|
+
size: chatGptSize
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
export {
|
|
1401
|
+
generateChatGPTWebImage,
|
|
1402
|
+
sha3_512Digest,
|
|
1403
|
+
solveTurnstileToken
|
|
1404
|
+
};
|