fxlinux commited on
Commit
0eaa409
·
verified ·
1 Parent(s): 4c6edba

Create main.ts

Browse files
Files changed (1) hide show
  1. main.ts +938 -0
main.ts ADDED
@@ -0,0 +1,938 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // deno run --allow-net --allow-read --allow-write server.ts
2
+
3
+ import { serve } from "https://deno.land/std@0.203.0/http/server.ts";
4
+
5
+ // =============================================================================
6
+ // 配置区 (请根据你的需求修改)
7
+ // =============================================================================
8
+
9
+ // --- 服务器配置 ---
10
+ const API_KEY = "your-api-key-here"; // 设置你的API密钥
11
+ const SERVER_HOST = "0.0.0.0"; // 服务器地址(Deno Deploy使用0.0.0.0)
12
+ const SERVER_PORT = 8000; // 服务器监听端口
13
+
14
+ // --- 图片链接配置 ---
15
+ const CUSTOM_IMAGE_BASE_URL = ""; // 自定义图片基础URL,留空则使用自动检测
16
+ const INCLUDE_PORT_IN_URL = false; // 是否在URL中包含端口号(Deno Deploy通常不需要)
17
+ const CUSTOM_PORT = 8000; // 自定义端口号(当INCLUDE_PORT_IN_URL为true时使用)
18
+
19
+ // --- 图片服务配置 ---
20
+ const IMAGE_DIR = "/tmp/public/images"; // 图片存储目录(使用/tmp目录)
21
+ const IMAGE_EXPIRE_HOURS = 1; // 图片在本地保存的小时数
22
+ const ENABLE_IMAGE_STORAGE = true; // 是否启用图片存储(Deno Deploy设为false)
23
+ const RETURN_BASE64_IMAGES = true; // 是否返回base64格式的图片(false为链接返回,需开启缓存)
24
+
25
+ // --- Venice API 配置 ---
26
+ const VENICE_CHAT_URL = "https://outerface.venice.ai/api/inference/chat";
27
+ const VENICE_IMAGE_URL = "https://outerface.venice.ai/api/inference/image";
28
+ const VENICE_VERSION = "interface@20251007.055834+464da4e";
29
+
30
+ // --- Cloudflare 代理配置 ---
31
+ const CF_IP_API_URL = "https://ipdb.api.030101.xyz/?type=cfv4;proxy";
32
+ const CF_HTTPS_PORTS = [443, 2053, 2083, 2087, 2096, 8443];
33
+ const CF_HTTP_PORTS = [80, 8080, 8880, 2052, 2082, 2086, 2095];
34
+ const USE_CF_AS_PROXY = true; // 是否使用Cloudflare IP作为代理
35
+ const PROXY_ROTATION_ENABLED = true; // 是否启用代理轮换
36
+ const MAX_REQUESTS_PER_PROXY = 1; // 每个代理最大请求数
37
+
38
+ // --- 模型配置 ---
39
+ const IMAGE_MODELS = ["stable-diffusion-3.5-rev2", "qwen-image", "hidream"]; // 画图模型列表
40
+
41
+ // =============================================================================
42
+ // 核心逻辑区 (通常无需修改)
43
+ // =============================================================================
44
+
45
+ // --- 全局状态 ---
46
+ let cfProxies: Array<{ip: string, port: number, lastUsed: number}> = [];
47
+ let currentProxyIndex = 0;
48
+
49
+ // --- 内存存储(用于Deno Deploy) ---
50
+ const imageStore = new Map<string, Uint8Array>();
51
+
52
+ // --- 随机 User-Agent ---
53
+ const USER_AGENTS = [
54
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
55
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
56
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
57
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
58
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
59
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0",
60
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
61
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0",
62
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
63
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
64
+ ];
65
+
66
+ // --- 工具函数 ---
67
+
68
+ function getRandomUserAgent(): string {
69
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
70
+ }
71
+
72
+ function validateApiKey(req: Request): boolean {
73
+ const authHeader = req.headers.get("Authorization");
74
+ if (!authHeader) return false;
75
+ const match = authHeader.match(/^Bearer\s+(.+)$/);
76
+ if (!match) return false;
77
+ return match[1] === API_KEY;
78
+ }
79
+
80
+ // CORS处理函数
81
+ function addCorsHeaders(response: Response): Response {
82
+ response.headers.set("Access-Control-Allow-Origin", "*");
83
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
84
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
85
+ return response;
86
+ }
87
+
88
+ // 简单的字符串哈希函数
89
+ function simpleHash(str: string): string {
90
+ let hash = 0;
91
+ for (let i = 0; i < str.length; i++) {
92
+ const char = str.charCodeAt(i);
93
+ hash = ((hash << 5) - hash) + char;
94
+ hash = hash & hash; // 转换为32位整数
95
+ }
96
+ return Math.abs(hash).toString(16);
97
+ }
98
+
99
+ // 生成动态用户ID
100
+ function generateDynamicUserId(req?: Request): string {
101
+ const timestamp = Date.now();
102
+ const random = Math.floor(Math.random() * 1000000);
103
+ const randomId = crypto.randomUUID().slice(0, 8);
104
+
105
+ if (req) {
106
+ const ip = req.headers.get("x-forwarded-for") ||
107
+ req.headers.get("x-real-ip") ||
108
+ "unknown";
109
+ const userAgent = req.headers.get("user-agent") || "unknown";
110
+
111
+ const hashInput = `${ip}-${userAgent}-${timestamp}-${random}`;
112
+ const hashHex = simpleHash(hashInput);
113
+
114
+ return `user_${hashHex.slice(0, 6)}_${randomId}_${random}`;
115
+ }
116
+
117
+ return `user_anon_${randomId}_${random}`;
118
+ }
119
+
120
+ // 生成图片URL的函数
121
+ function generateImageUrl(filename: string, req?: Request): string {
122
+ if (CUSTOM_IMAGE_BASE_URL.trim()) {
123
+ let baseUrl = CUSTOM_IMAGE_BASE_URL.trim();
124
+ if (baseUrl.endsWith('/')) {
125
+ baseUrl = baseUrl.slice(0, -1);
126
+ }
127
+
128
+ if (INCLUDE_PORT_IN_URL && CUSTOM_PORT) {
129
+ const hasPort = baseUrl.match(/:(\d+)$/);
130
+ if (!hasPort) {
131
+ baseUrl += `:${CUSTOM_PORT}`;
132
+ }
133
+ }
134
+
135
+ return `${baseUrl}/images/${filename}`;
136
+ }
137
+
138
+ const protocol = "https";
139
+ let host = "your-domain.deno.dev";
140
+
141
+ if (req) {
142
+ const reqHost = req.headers.get("host");
143
+ if (reqHost) {
144
+ host = reqHost;
145
+ }
146
+ }
147
+
148
+ let url = `${protocol}://${host}`;
149
+
150
+ if (INCLUDE_PORT_IN_URL && CUSTOM_PORT) {
151
+ if ((protocol === 'http' && CUSTOM_PORT !== 80) ||
152
+ (protocol === 'https' && CUSTOM_PORT !== 443)) {
153
+ url += `:${CUSTOM_PORT}`;
154
+ }
155
+ }
156
+
157
+ return `${url}/images/${filename}`;
158
+ }
159
+
160
+ // 将图片数据转换为base64格式
161
+ function arrayBufferToBase64(buffer: ArrayBuffer): string {
162
+ let binary = '';
163
+ const bytes = new Uint8Array(buffer);
164
+ const len = bytes.byteLength;
165
+ for (let i = 0; i < len; i++) {
166
+ binary += String.fromCharCode(bytes[i]);
167
+ }
168
+ return btoa(binary);
169
+ }
170
+
171
+ // --- Cloudflare 代理管理 ---
172
+
173
+ async function fetchAndUpdateCfProxies() {
174
+ try {
175
+ console.log("正在从API获取最新的Cloudflare代理列表...");
176
+
177
+ const response = await fetch(CF_IP_API_URL, {
178
+ headers: {
179
+ "User-Agent": getRandomUserAgent(),
180
+ "Accept": "text/plain",
181
+ }
182
+ });
183
+
184
+ if (!response.ok) throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
185
+ const text = await response.text();
186
+ const ips = text.split('\n').filter(ip => ip.trim() !== '');
187
+
188
+ // 创建代理列表,每个IP配对多个端口
189
+ cfProxies = [];
190
+ const httpsPorts = CF_HTTPS_PORTS;
191
+
192
+ for (const ip of ips) {
193
+ for (const port of httpsPorts) {
194
+ cfProxies.push({
195
+ ip: ip.trim(),
196
+ port: port,
197
+ lastUsed: 0
198
+ });
199
+ }
200
+ }
201
+
202
+ console.log(`成功创建 ${cfProxies.length} 个Cloudflare代理端点。`);
203
+ if (cfProxies.length === 0) {
204
+ console.error("警告:没有可用的代理端点。");
205
+ }
206
+ } catch (error) {
207
+ console.error("获取Cloudflare代理列表时出错:", error);
208
+ }
209
+ }
210
+
211
+ // 获取下一个可用的代理
212
+ function getNextProxy(): {ip: string, port: number} | null {
213
+ if (cfProxies.length === 0) {
214
+ console.error("代理列表为空,无法获取代理。");
215
+ return null;
216
+ }
217
+
218
+ // 查找最少使用的代理
219
+ let bestProxy = cfProxies[0];
220
+ let bestIndex = 0;
221
+
222
+ for (let i = 0; i < cfProxies.length; i++) {
223
+ const proxy = cfProxies[i];
224
+ if (proxy.lastUsed < bestProxy.lastUsed) {
225
+ bestProxy = proxy;
226
+ bestIndex = i;
227
+ }
228
+ }
229
+
230
+ // 更新使用时间
231
+ bestProxy.lastUsed = Date.now();
232
+ cfProxies[bestIndex] = bestProxy;
233
+
234
+ console.log(`[代理轮换] 使用代理: ${bestProxy.ip}:${bestProxy.port}`);
235
+ return { ip: bestProxy.ip, port: bestProxy.port };
236
+ }
237
+
238
+ // 通过Cloudflare代理发起请求
239
+ async function fetchThroughCloudflareProxy(url: string, options: RequestInit = {}): Promise<Response> {
240
+ const proxy = getNextProxy();
241
+ if (!proxy) {
242
+ throw new Error("没有可用的代理");
243
+ }
244
+
245
+ console.log(`[代理请求] 通过 ${proxy.ip}:${proxy.port} 请求: ${url}`);
246
+
247
+ // 创建HTTP客户端,通过Cloudflare IP连接
248
+ const client = Deno.createHttpClient({
249
+ connect: {
250
+ hostname: proxy.ip,
251
+ port: proxy.port,
252
+ }
253
+ });
254
+
255
+ try {
256
+ // 构建请求头,模拟真实浏览器
257
+ const defaultHeaders = {
258
+ "User-Agent": getRandomUserAgent(),
259
+ "Accept": "application/json, text/plain, */*",
260
+ "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
261
+ "Accept-Encoding": "gzip, deflate, br",
262
+ "Connection": "keep-alive",
263
+ "Sec-Fetch-Dest": "empty",
264
+ "Sec-Fetch-Mode": "cors",
265
+ "Sec-Fetch-Site": "cross-site",
266
+ "Sec-Ch-Ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
267
+ "Sec-Ch-Ua-Mobile": "?0",
268
+ "Sec-Ch-Ua-Platform": '"Windows"',
269
+ "Cache-Control": "no-cache",
270
+ "Pragma": "no-cache",
271
+ // 添加伪造的IP头
272
+ "X-Forwarded-For": `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`,
273
+ "X-Real-IP": `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`,
274
+ ...options.headers,
275
+ };
276
+
277
+ const response = await fetch(url, {
278
+ ...options,
279
+ client,
280
+ headers: defaultHeaders,
281
+ });
282
+
283
+ console.log(`[代理响应] 状态: ${response.status} ${response.statusText}`);
284
+
285
+ // 记录速率限制信息
286
+ const remainingRequests = response.headers.get('x-ratelimit-remaining-requests');
287
+ const resetRequests = response.headers.get('x-ratelimit-reset-requests');
288
+ if (remainingRequests || resetRequests) {
289
+ console.log(`[速率限制] 剩余: ${remainingRequests}, 重置: ${resetRequests}`);
290
+ }
291
+
292
+ return response;
293
+ } finally {
294
+ client.close();
295
+ }
296
+ }
297
+
298
+ // 带重试机制的请求函数
299
+ async function fetchWithRetry(url: string, options: RequestInit = {}, maxRetries: number = 3): Promise<Response> {
300
+ let attempt = 0;
301
+
302
+ while (attempt < maxRetries) {
303
+ try {
304
+ const response = await fetchThroughCloudflareProxy(url, options);
305
+
306
+ if (response.status !== 429) {
307
+ return response;
308
+ }
309
+
310
+ console.warn(`[429错误] 第${attempt + 1}次尝试收到429错误`);
311
+
312
+ const resetTimeHeader = response.headers.get('x-ratelimit-reset-requests');
313
+ let waitTime = 5000;
314
+
315
+ if (resetTimeHeader) {
316
+ const resetTime = parseInt(resetTimeHeader, 10) * 1000;
317
+ waitTime = Math.max(resetTime - Date.now(), 2000);
318
+ }
319
+
320
+ console.log(`[重试] 等待 ${waitTime}ms 后重试...`);
321
+ await new Promise(resolve => setTimeout(resolve, waitTime));
322
+
323
+ // 强制更换代理
324
+ const nextProxy = getNextProxy();
325
+ if (nextProxy) {
326
+ console.log(`[强制换代理] 切换到: ${nextProxy.ip}:${nextProxy.port}`);
327
+ }
328
+
329
+ } catch (error) {
330
+ console.error(`请求失败 (尝试 ${attempt + 1}/${maxRetries}):`, error);
331
+ if (attempt === maxRetries - 1) throw error;
332
+
333
+ const backoffTime = Math.pow(2, attempt) * 1000;
334
+ await new Promise(resolve => setTimeout(resolve, backoffTime));
335
+ }
336
+ attempt++;
337
+ }
338
+
339
+ throw new Error('达到最大重试次数');
340
+ }
341
+
342
+ // --- 图片管理 ---
343
+
344
+ async function ensureImageDir() {
345
+ if (!ENABLE_IMAGE_STORAGE) {
346
+ console.log("图片存储已禁用,使用内存存储");
347
+ return;
348
+ }
349
+
350
+ try {
351
+ await Deno.mkdir(IMAGE_DIR, { recursive: true });
352
+ console.log(`图片目录已准备就绪: ${IMAGE_DIR}`);
353
+ } catch (error) {
354
+ console.error(`创建图片目录失败: ${error}`);
355
+ console.log("将使用内存存储替代文件存储");
356
+ }
357
+ }
358
+
359
+ async function cleanOldImages() {
360
+ if (!ENABLE_IMAGE_STORAGE) {
361
+ return;
362
+ }
363
+
364
+ try {
365
+ console.log("[清理任务] 开始清理旧图片...");
366
+ const expireTime = Date.now() - IMAGE_EXPIRE_HOURS * 60 * 60 * 1000;
367
+ let deletedCount = 0;
368
+ for await (const entry of Deno.readDir(IMAGE_DIR)) {
369
+ if (entry.isFile) {
370
+ const filePath = `${IMAGE_DIR}/${entry.name}`;
371
+ const fileInfo = await Deno.stat(filePath);
372
+ if (fileInfo.mtime?.getTime() && fileInfo.mtime.getTime() < expireTime) {
373
+ await Deno.remove(filePath);
374
+ deletedCount++;
375
+ }
376
+ }
377
+ }
378
+ console.log(`[清理任务] 完成,删除了 ${deletedCount} 个旧图片文件。`);
379
+ } catch (error) {
380
+ console.error("[清理任务] 清理失败:", error);
381
+ }
382
+ }
383
+
384
+ // --- API 响应格式 ---
385
+
386
+ function openaiModels() {
387
+ return {
388
+ object: "list",
389
+ data: [
390
+ { id: "dolphin-3.0-mistral-24b-1dot1", object: "model", created: 1690000000, owned_by: "venice.ai" },
391
+ { id: "mistral-31-24b", object: "model", created: 1690000001, owned_by: "venice.ai" },
392
+ { id: "stable-diffusion-3.5-rev2", object: "model", created: 1690000002, owned_by: "venice.ai" },
393
+ { id: "qwen-image", object: "model", created: 1690000003, owned_by: "venice.ai" },
394
+ { id: "hidream", object: "model", created: 1690000004, owned_by: "venice.ai" },
395
+ ],
396
+ };
397
+ }
398
+
399
+ // --- 请求处理函数 ---
400
+
401
+ async function handleImageGeneration(model: string, prompt: string, size: string, negativePrompt: string, req: Request): Promise<Response> {
402
+ try {
403
+ const userId = generateDynamicUserId(req);
404
+ console.log(`[图片生成] 使用动态用户ID: ${userId}`);
405
+
406
+ const [width, height] = size.split('x').map(Number);
407
+ const venicePayload = {
408
+ aspectRatio: `${width}:${height}`, embedExifMetadata: true, format: "webp", height, hideWatermark: false,
409
+ imageToImageCfgScale: 15, imageToImageStrength: 33, loraStrength: 75, matureFilter: true,
410
+ messageId: crypto.randomUUID().slice(0, 8), modelId: model, negativePrompt, parentMessageId: null, prompt,
411
+ requestId: crypto.randomUUID().slice(0, 8), seed: Math.floor(Math.random() * 2**31),
412
+ steps: model === "hidream" || model === "qwen-image" ? 20 : 25, stylePreset: "None", type: "image",
413
+ userId: userId,
414
+ variants: 1, width,
415
+ };
416
+ const headers = {
417
+ "Content-Type": "application/json",
418
+ "Origin": "https://venice.ai",
419
+ "Referer": "https://venice.ai/",
420
+ "User-Agent": getRandomUserAgent(),
421
+ "Accept": "application/json, image/*",
422
+ "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
423
+ "Accept-Encoding": "gzip, deflate, br",
424
+ "Cache-Control": "no-cache",
425
+ "Pragma": "no-cache",
426
+ "Sec-Fetch-Dest": "empty",
427
+ "Sec-Fetch-Mode": "cors",
428
+ "Sec-Fetch-Site": "cross-site",
429
+ "x-venice-timestamp": new Date().toISOString(),
430
+ "x-venice-version": VENICE_VERSION
431
+ };
432
+
433
+ console.log(`[图片生成] 请求Venice API: ${VENICE_IMAGE_URL}`);
434
+
435
+ const veniceResp = await fetchWithRetry(VENICE_IMAGE_URL, {
436
+ method: "POST",
437
+ headers,
438
+ body: JSON.stringify(venicePayload)
439
+ });
440
+
441
+ console.log(`[图片生成] Venice API响应状态: ${veniceResp.status} ${veniceResp.statusText}`);
442
+
443
+ if (!veniceResp.ok) {
444
+ const errorText = await veniceResp.text();
445
+ console.error(`[错误] Venice API返回错误 (${veniceResp.status}): ${errorText}`);
446
+ const errorMarkdown = `# 图片生成失败
447
+
448
+ **错误信息:**
449
+ - 状态码:${veniceResp.status}
450
+ - 详情:${errorText}
451
+
452
+ 请检查请求参数后重试。`;
453
+ return addCorsHeaders(new Response(errorMarkdown, {
454
+ status: veniceResp.status,
455
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
456
+ }));
457
+ }
458
+
459
+ const imageBuffer = await veniceResp.arrayBuffer();
460
+ if (imageBuffer.byteLength === 0) {
461
+ const errorMarkdown = `# 图片生成失败
462
+
463
+ **错误信息:**
464
+ - 原因:接收到空的图片数据
465
+
466
+ 请稍后重试。`;
467
+ return addCorsHeaders(new Response(errorMarkdown, {
468
+ status: 500,
469
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
470
+ }));
471
+ }
472
+
473
+ const filename = `${crypto.randomUUID()}.webp`;
474
+ const imageData = new Uint8Array(imageBuffer);
475
+
476
+ if (ENABLE_IMAGE_STORAGE) {
477
+ try {
478
+ const filePath = `${IMAGE_DIR}/${filename}`;
479
+ await Deno.writeFile(filePath, imageData);
480
+ console.log(`[成功] 图片已保存: ${filename}`);
481
+ } catch (error) {
482
+ console.error(`保存图片失败: ${error},使用内存存储`);
483
+ imageStore.set(filename, imageData);
484
+ }
485
+ } else {
486
+ imageStore.set(filename, imageData);
487
+ console.log(`[成功] 图片已保存到内存: ${filename}`);
488
+ }
489
+
490
+ if (RETURN_BASE64_IMAGES) {
491
+ const base64Image = arrayBufferToBase64(imageBuffer);
492
+ const dataUrl = `data:image/webp;base64,${base64Image}`;
493
+
494
+ const readmeResponse = `![${prompt}](${dataUrl})
495
+
496
+ ## 图片信息
497
+
498
+ - **模型**:${model}
499
+ - **提示词**:${prompt}
500
+ - **尺寸**:${size}
501
+ - **负面提示词**:${negativePrompt || '无'}
502
+
503
+ ## 图片数据
504
+
505
+ Base64编码的图片数据已包含在上方图片中。`;
506
+
507
+ return addCorsHeaders(new Response(readmeResponse, {
508
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
509
+ }));
510
+ } else {
511
+ const imageUrl = generateImageUrl(filename, req);
512
+
513
+ const readmeResponse = `![${prompt}](${imageUrl})
514
+
515
+ ## 图片信息
516
+
517
+ - **模型**:${model}
518
+ - **提示词**:${prompt}
519
+ - **尺寸**:${size}
520
+ - **负面提示词**:${negativePrompt || '无'}
521
+
522
+ ## 图片链接
523
+
524
+ ${imageUrl}`;
525
+
526
+ return addCorsHeaders(new Response(readmeResponse, {
527
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
528
+ }));
529
+ }
530
+ } catch (error) {
531
+ console.error("Image generation request failed:", error);
532
+ const errorMarkdown = `# 图片生成失败
533
+
534
+ **错误信息:**
535
+ - 原因:${error.message || '未知错误'}
536
+
537
+ 请稍后重试。`;
538
+ return addCorsHeaders(new Response(errorMarkdown, {
539
+ status: 500,
540
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
541
+ }));
542
+ }
543
+ }
544
+
545
+ async function handleChatCompletion(model: string, messages: any[], temperature: number, topP: number, stream: boolean, req: Request): Promise<Response> {
546
+ try {
547
+ const userId = generateDynamicUserId(req);
548
+ console.log(`[聊天] 使用动态用户ID: ${userId}`);
549
+
550
+ const venicePayload = {
551
+ characterId: "",
552
+ clientProcessingTime: 2,
553
+ conversationType: "text",
554
+ includeVeniceSystemPrompt: true,
555
+ isCharacter: false,
556
+ modelId: model,
557
+ prompt: messages,
558
+ reasoning: true,
559
+ requestId: crypto.randomUUID().slice(0, 8),
560
+ systemPrompt: "",
561
+ temperature,
562
+ topP,
563
+ userId: userId,
564
+ webEnabled: true
565
+ };
566
+ const headers = {
567
+ "Content-Type": "application/json",
568
+ "Origin": "https://venice.ai",
569
+ "Referer": "https://venice.ai/",
570
+ "User-Agent": getRandomUserAgent(),
571
+ "Accept": "text/event-stream, application/json, text/plain",
572
+ "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
573
+ "Accept-Encoding": "gzip, deflate, br",
574
+ "Cache-Control": "no-cache",
575
+ "Connection": "keep-alive",
576
+ "Sec-Fetch-Dest": "empty",
577
+ "Sec-Fetch-Mode": "cors",
578
+ "Sec-Fetch-Site": "cross-site",
579
+ };
580
+
581
+ console.log(`[聊天] 请求Venice API: ${VENICE_CHAT_URL}`);
582
+
583
+ const veniceResp = await fetchWithRetry(VENICE_CHAT_URL, {
584
+ method: "POST",
585
+ headers,
586
+ body: JSON.stringify(venicePayload)
587
+ });
588
+
589
+ console.log(`[聊天] Venice API响应状态: ${veniceResp.status} ${veniceResp.statusText}`);
590
+
591
+ if (!veniceResp.ok) {
592
+ const errorText = await veniceResp.text();
593
+ console.error(`[错误] Venice API返回错误:`, errorText);
594
+ return addCorsHeaders(new Response(JSON.stringify({
595
+ error: {
596
+ message: `Venice API error: ${veniceResp.status}`,
597
+ details: errorText,
598
+ type: "venice_api_error"
599
+ }
600
+ }), {
601
+ status: veniceResp.status,
602
+ headers: { "Content-Type": "application/json" }
603
+ }));
604
+ }
605
+
606
+ if (stream) {
607
+ const reader = veniceResp.body?.getReader();
608
+ const encoder = new TextEncoder();
609
+ const decoder = new TextDecoder();
610
+ let buffer = "";
611
+ let isFinished = false;
612
+
613
+ const streamResp = new ReadableStream({
614
+ async start(controller) {
615
+ const timeoutId = setTimeout(() => {
616
+ if (!isFinished) {
617
+ console.error("Stream timeout, closing connection");
618
+ controller.close();
619
+ }
620
+ }, 60000);
621
+
622
+ try {
623
+ while (!isFinished) {
624
+ if (!reader) {
625
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
626
+ controller.close();
627
+ isFinished = true;
628
+ break;
629
+ }
630
+ const { done, value } = await reader.read();
631
+ if (done) {
632
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
633
+ controller.close();
634
+ isFinished = true;
635
+ break;
636
+ }
637
+ const chunk = decoder.decode(value, { stream: true });
638
+ buffer += chunk;
639
+ let idx;
640
+ while ((idx = buffer.indexOf("\n")) >= 0) {
641
+ const line = buffer.slice(0, idx).trim();
642
+ buffer = buffer.slice(idx + 1);
643
+ if (!line) continue;
644
+ try {
645
+ const data = JSON.parse(line);
646
+ const content = data.content;
647
+ if (content) {
648
+ const chunk = {
649
+ id: `chatcmpl-${crypto.randomUUID().slice(0, 8)}`,
650
+ object: "chat.completion.chunk",
651
+ created: Math.floor(Date.now() / 1000),
652
+ model,
653
+ choices: [{
654
+ delta: { content },
655
+ index: 0,
656
+ finish_reason: null,
657
+ }],
658
+ };
659
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
660
+ }
661
+ } catch (parseError) {
662
+ console.error("JSON parse error:", parseError, "Line:", line);
663
+ }
664
+ }
665
+ }
666
+ } catch (error) {
667
+ console.error("Stream processing error:", error);
668
+ const errorChunk = {
669
+ id: `chatcmpl-${crypto.randomUUID().slice(0, 8)}`,
670
+ object: "chat.completion.chunk",
671
+ created: Math.floor(Date.now() / 1000),
672
+ model,
673
+ choices: [
674
+ {
675
+ delta: { content: "\n\n[Stream interrupted due to error]" },
676
+ index: 0,
677
+ finish_reason: "error",
678
+ },
679
+ ],
680
+ };
681
+ controller.enqueue(
682
+ encoder.encode(`data: ${JSON.stringify(errorChunk)}\n\n`),
683
+ );
684
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
685
+ controller.close();
686
+ } finally {
687
+ clearTimeout(timeoutId);
688
+ isFinished = true;
689
+ }
690
+ }
691
+ });
692
+
693
+ const response = new Response(streamResp, {
694
+ headers: {
695
+ "Content-Type": "text/event-stream",
696
+ "Cache-Control": "no-cache",
697
+ "Connection": "keep-alive",
698
+ "Access-Control-Allow-Origin": "*",
699
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
700
+ },
701
+ });
702
+ return response;
703
+ } else {
704
+ const text = await veniceResp.text();
705
+ console.log(`[聊天] Venice API响应内容:`, text);
706
+ const contents = text.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l).content).join("");
707
+ const resp = {
708
+ id: `chatcmpl-${crypto.randomUUID().slice(0, 8)}`,
709
+ object: "chat.completion",
710
+ created: Math.floor(Date.now() / 1000),
711
+ model,
712
+ choices: [{
713
+ index: 0,
714
+ message: { role: "assistant", content: contents },
715
+ finish_reason: "stop",
716
+ }],
717
+ };
718
+ return addCorsHeaders(new Response(JSON.stringify(resp), { headers: { "Content-Type": "application/json" }, }));
719
+ }
720
+ } catch (error) {
721
+ console.error("Chat completion request failed:", error);
722
+ return addCorsHeaders(new Response(JSON.stringify({
723
+ error: {
724
+ message: "Failed to process chat completion",
725
+ type: "request_error",
726
+ details: error.message
727
+ }
728
+ }), {
729
+ status: 500,
730
+ headers: { "Content-Type": "application/json" }
731
+ }));
732
+ }
733
+ }
734
+
735
+ // --- 服务器启动 ---
736
+
737
+ async function initializeServer() {
738
+ await ensureImageDir();
739
+ await fetchAndUpdateCfProxies();
740
+ if (cfProxies.length === 0) console.error("无法获取任何Cloudflare代理,服务将启动但可能无法正常工作。");
741
+
742
+ if (ENABLE_IMAGE_STORAGE) {
743
+ setInterval(fetchAndUpdateCfProxies, 5 * 60 * 1000);
744
+ setInterval(cleanOldImages, IMAGE_EXPIRE_HOURS * 60 * 60 * 1000);
745
+ }
746
+
747
+ console.log(`服务器已启动,监听端口 ${SERVER_PORT}...`);
748
+
749
+ if (CUSTOM_IMAGE_BASE_URL.trim()) {
750
+ console.log(`使用自定义图片基础URL: ${CUSTOM_IMAGE_BASE_URL}`);
751
+ if (INCLUDE_PORT_IN_URL && CUSTOM_PORT) {
752
+ console.log(`端口配置: 包含端口 ${CUSTOM_PORT}`);
753
+ } else {
754
+ console.log(`端口配置: 不包含端口`);
755
+ }
756
+ } else {
757
+ console.log(`图片将通过自动检测的URL访问`);
758
+ console.log(`当前配置: ${SERVER_HOST}:${SERVER_PORT}`);
759
+ }
760
+
761
+ console.log(`Cloudflare代理: ${USE_CF_AS_PROXY ? '启用' : '禁用'}`);
762
+ if (USE_CF_AS_PROXY) {
763
+ console.log(`可用代理数量: ${cfProxies.length}`);
764
+ }
765
+ console.log(`图片存储: ${ENABLE_IMAGE_STORAGE ? '文件存储' : '内存存储'}`);
766
+ console.log(`图片返回格式: ${RETURN_BASE64_IMAGES ? 'Base64' : 'URL'}`);
767
+
768
+ serve(async (req: Request) => {
769
+ const url = new URL(req.url);
770
+
771
+ if (req.method === "OPTIONS") {
772
+ return addCorsHeaders(new Response(null, { status: 200 }));
773
+ }
774
+
775
+ // 添加IP测试端点
776
+ if (url.pathname === "/test-ip" && req.method === "GET") {
777
+ try {
778
+ const testUrl = "https://httpbin.org/ip";
779
+ const response = await fetchThroughCloudflareProxy(testUrl);
780
+ const result = await response.json();
781
+ return addCorsHeaders(new Response(JSON.stringify({
782
+ proxyIP: result.origin,
783
+ proxyEnabled: USE_CF_AS_PROXY,
784
+ totalProxies: cfProxies.length,
785
+ timestamp: Date.now()
786
+ }), { headers: { "Content-Type": "application/json" } }));
787
+ } catch (error) {
788
+ return addCorsHeaders(new Response(JSON.stringify({
789
+ error: error.message
790
+ }), { status: 500 }));
791
+ }
792
+ }
793
+
794
+ // --- 静态图片服务 ---
795
+ if (url.pathname.startsWith("/images/")) {
796
+ const filename = url.pathname.substring("/images/".length);
797
+
798
+ if (imageStore.has(filename)) {
799
+ const imageData = imageStore.get(filename);
800
+
801
+ if (url.searchParams.get('format') === 'base64') {
802
+ const base64Image = arrayBufferToBase64(imageData.buffer);
803
+ const dataUrl = `data:image/webp;base64,${base64Image}`;
804
+ return addCorsHeaders(new Response(JSON.stringify({
805
+ dataUrl,
806
+ filename
807
+ }), {
808
+ headers: {
809
+ "Content-Type": "application/json",
810
+ "Cache-Control": `public, max-age=${IMAGE_EXPIRE_HOURS * 3600}`
811
+ }
812
+ }));
813
+ }
814
+
815
+ return addCorsHeaders(new Response(imageData, {
816
+ headers: {
817
+ "Content-Type": "image/webp",
818
+ "Cache-Control": `public, max-age=${IMAGE_EXPIRE_HOURS * 3600}`
819
+ }
820
+ }));
821
+ }
822
+
823
+ if (ENABLE_IMAGE_STORAGE) {
824
+ try {
825
+ const filePath = `${IMAGE_DIR}/${filename}`;
826
+ const imageFile = await Deno.readFile(filePath);
827
+
828
+ if (url.searchParams.get('format') === 'base64') {
829
+ const base64Image = arrayBufferToBase64(imageFile.buffer);
830
+ const dataUrl = `data:image/webp;base64,${base64Image}`;
831
+ return addCorsHeaders(new Response(JSON.stringify({
832
+ dataUrl,
833
+ filename
834
+ }), {
835
+ headers: {
836
+ "Content-Type": "application/json",
837
+ "Cache-Control": `public, max-age=${IMAGE_EXPIRE_HOURS * 3600}`
838
+ }
839
+ }));
840
+ }
841
+
842
+ return addCorsHeaders(new Response(imageFile, {
843
+ headers: {
844
+ "Content-Type": "image/webp",
845
+ "Cache-Control": `public, max-age=${IMAGE_EXPIRE_HOURS * 3600}`
846
+ }
847
+ }));
848
+ } catch (error) {
849
+ console.error(`读取图片文件失败: ${error}`);
850
+ }
851
+ }
852
+
853
+ return addCorsHeaders(new Response("Image Not Found", { status: 404 }));
854
+ }
855
+
856
+ // --- API 密钥验证 ---
857
+ if (url.pathname === "/v1/models" || url.pathname === "/v1/chat/completions") {
858
+ if (!validateApiKey(req)) {
859
+ return addCorsHeaders(new Response(JSON.stringify({
860
+ error: {
861
+ message: "Invalid API key",
862
+ type: "invalid_request_error",
863
+ code: "invalid_api_key"
864
+ }
865
+ }), {
866
+ status: 401,
867
+ headers: { "Content-Type": "application/json" }
868
+ }));
869
+ }
870
+ }
871
+
872
+ // --- 路由分发 ---
873
+ if (url.pathname === "/v1/models") {
874
+ return addCorsHeaders(new Response(JSON.stringify(openaiModels()), { headers: { "Content-Type": "application/json" } }));
875
+ }
876
+
877
+ if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
878
+ try {
879
+ const body = await req.json();
880
+ const model = body.model ?? "dolphin-3.0-mistral-24b-1dot1";
881
+ const messages = body.messages ?? [];
882
+ const temperature = body.temperature ?? 0.7;
883
+ const topP = body.top_p ?? 0.9;
884
+ const stream = body.stream ?? false;
885
+
886
+ if (IMAGE_MODELS.includes(model)) {
887
+ console.log(`[请求类型] 画图 - 模型: ${model}`);
888
+ const lastUserMessage = messages.filter(m => m.role === 'user').pop();
889
+ const prompt = lastUserMessage?.content;
890
+ if (!prompt || typeof prompt !== 'string') {
891
+ const errorMarkdown = `# 请求错误
892
+
893
+ **错误信息:**
894
+ - 原因:图片生成需要文本提示词
895
+ - 要求:请在最后一条用户消息中提供提示词
896
+
897
+ 示例:
898
+ \`\`\`json
899
+ {
900
+ "model": "stable-diffusion-3.5-rev2",
901
+ "messages": [
902
+ {"role": "user", "content": "a beautiful sunset"}
903
+ ]
904
+ }
905
+ \`\`\``;
906
+ return addCorsHeaders(new Response(errorMarkdown, {
907
+ status: 400,
908
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
909
+ }));
910
+ }
911
+ const size = body.size ?? "1024x1024";
912
+ const negativePrompt = body.negative_prompt ?? "";
913
+ return await handleImageGeneration(model, prompt, size, negativePrompt, req);
914
+ } else {
915
+ console.log(`[请求类型] 聊天 - 模型: ${model}`);
916
+ return await handleChatCompletion(model, messages, temperature, topP, stream, req);
917
+ }
918
+ } catch (error) {
919
+ console.error("Request processing error:", error);
920
+ const errorMarkdown = `# 请求处理失败
921
+
922
+ **错误信息:**
923
+ - 原因:${error.message || '未知错误'}
924
+
925
+ 请检查请求格式后重试。`;
926
+ return addCorsHeaders(new Response(errorMarkdown, {
927
+ status: 500,
928
+ headers: { "Content-Type": "text/markdown; charset=utf-8" }
929
+ }));
930
+ }
931
+ }
932
+
933
+ return addCorsHeaders(new Response("Not Found", { status: 404 }));
934
+ }, { port: SERVER_PORT });
935
+ }
936
+
937
+ // 启动
938
+ initializeServer();