caidaohz commited on
Commit
24b6bbe
·
1 Parent(s): b5445bb

Remove main application code, dependencies, and versioning files

Browse files
Files changed (9) hide show
  1. Dockerfile +10 -22
  2. README.md +7 -9
  3. app.py +0 -397
  4. go.mod +36 -0
  5. go.sum +90 -0
  6. main.go +750 -0
  7. main.ts +0 -679
  8. requirements.txt +0 -8
  9. version.py +0 -1
Dockerfile CHANGED
@@ -1,26 +1,14 @@
1
- # 使用官方的Python基础镜像
2
- FROM python:3.10-slim
3
 
4
- # 安装时区依赖
5
- RUN apt-get update && apt-get install -y tzdata
6
-
7
- # 设置东八区时区
8
- ENV TZ=Asia/Shanghai
9
-
10
- # 设置工作目录
11
  WORKDIR /app
12
-
13
- # 复制依赖文件到工作目录
14
- COPY requirements.txt .
15
-
16
- # 安装依赖
17
- RUN pip install --no-cache-dir -r requirements.txt
18
-
19
- # 复制应用代码到工作目录
20
  COPY . .
 
21
 
22
- # 暴露端口
23
- EXPOSE 8000
24
-
25
- # 运行应用
26
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
1
+ # 使用官方Go基础镜像
2
+ FROM golang:1.22-alpine as builder
3
 
 
 
 
 
 
 
 
4
  WORKDIR /app
 
 
 
 
 
 
 
 
5
  COPY . .
6
+ RUN go mod tidy && go build -o app .
7
 
8
+ FROM alpine:latest
9
+ WORKDIR /app
10
+ COPY --from=builder /app/app .
11
+ COPY --from=builder /app/version.py ./version.py
12
+ ENV TZ=Asia/Shanghai
13
+ EXPOSE 7860
14
+ CMD ["./app"]
README.md CHANGED
@@ -1,10 +1,8 @@
1
- ---
2
- title: AIChatbot2API
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
1
+ # AIChatbot2API (Go版)
 
 
 
 
 
 
 
2
 
3
+ 本项目为AIChatbot2API的Go语言实现,提供与原Python版本相同的API接口:
4
+ - /v1/chat/completions
5
+ - /v1/models
6
+ - /health
7
+
8
+ 请参考源码和Dockerfile进行部署。
app.py DELETED
@@ -1,397 +0,0 @@
1
- import re
2
- import os
3
- import uuid
4
- import time
5
- import json
6
- import httpx
7
- import uvicorn
8
- import hashlib
9
- import secrets
10
- import logging
11
- from pydantic import BaseModel
12
- from dotenv import load_dotenv
13
- from fake_useragent import UserAgent
14
- from fastapi.security import APIKeyHeader
15
- from typing import List, Literal, Optional
16
- from fastapi.responses import StreamingResponse
17
- from fastapi.responses import Response
18
- from fastapi.middleware.cors import CORSMiddleware
19
- from fastapi import FastAPI, HTTPException, Depends
20
-
21
- # Load .env file
22
- load_dotenv()
23
-
24
- # Retrieve environment variables
25
- API_KEY = os.getenv("API_KEY")
26
- if not API_KEY:
27
- raise ValueError("API_KEY not found in .env file")
28
-
29
- ENABLE_CORS = os.getenv("ENABLE_CORS", "True").lower() in ("true", "1", "yes")
30
- LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
31
- MAX_CHARS = int(os.getenv("MAX_CHARS", "80000"))
32
- RANDOM_UA = os.getenv("RANDOM_UA", "False").lower() in ("true", "1", "yes")
33
-
34
- # Configure logging
35
- logging.basicConfig(
36
- level=getattr(logging, LOG_LEVEL, logging.INFO),
37
- format="%(asctime)s - %(levelname)s - %(message)s",
38
- handlers=[logging.StreamHandler()]
39
- )
40
- logger = logging.getLogger(__name__)
41
-
42
- # Suppress httpx and httpcore debug logs
43
- logging.getLogger("httpx").setLevel(logging.ERROR)
44
- logging.getLogger("httpcore").setLevel(logging.ERROR)
45
-
46
- # Initialize FastAPI app
47
- app = FastAPI()
48
-
49
- # Enable CORS if configured
50
- if ENABLE_CORS:
51
- app.add_middleware(
52
- CORSMiddleware,
53
- allow_origins=["*"],
54
- allow_credentials=True,
55
- allow_methods=["*"],
56
- allow_headers=["*"],
57
- )
58
- logger.info("CORS enabled")
59
- else:
60
- logger.info("CORS disabled")
61
-
62
- # Constants
63
- api_domain = "https://ai-chatbot.top"
64
- default_user_agent = (
65
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) "
66
- "Gecko/20100101 Firefox/136.0"
67
- )
68
- ua = UserAgent()
69
-
70
- # Supported models
71
- supported_models = ["DeepSeek-R1", "DeepSeek-V3"]
72
- model_to_config = {
73
- "DeepSeek-R1": {"model": "deepseek-huoshan", "isWebSearchEnabled": False},
74
- "DeepSeek-V3": {"model": "deepseek-guiji", "isWebSearchEnabled": False},
75
- }
76
-
77
- # Device ID and UA mapping
78
- device_ua_map = {}
79
-
80
-
81
- # Utility functions
82
- def nanoid(size=21):
83
- url_alphabet = "abcdefgh0ijkl1mno2pqrs3tuv4wxyz5ABCDEFGH6IJKL7MNO8PQRS9TUV-WXYZ_"
84
- return "".join(secrets.choice(url_alphabet) for _ in range(size))
85
-
86
-
87
- def generate_device_id():
88
- return f"{uuid.uuid4().hex}_{nanoid(20)}"
89
-
90
-
91
- def get_user_agent(device_id: str) -> str:
92
- if not RANDOM_UA:
93
- return default_user_agent
94
- if device_id not in device_ua_map:
95
- device_ua_map[device_id] = ua.random
96
- return device_ua_map[device_id]
97
-
98
-
99
- def generate_sign(chat_id: str, timestamp: int) -> str:
100
- message = f"{chat_id}{timestamp}@!~chatbot.0868"
101
- return hashlib.md5(message.encode("utf-8")).hexdigest()
102
-
103
-
104
- async def get_chat_id() -> str:
105
- cookies = {
106
- '_ga_HVMZBNYJML': 'GS1.1.1742013194.1.0.1742013194.0.0.0',
107
- '_ga': 'GA1.1.1029622546.1742013195',
108
- }
109
- headers = {
110
- "User-Agent": default_user_agent,
111
- "Accept": "*/*",
112
- "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
113
- "Referer": "https://ai-chatbot.top/",
114
- "RSC": "1",
115
- "Next-Router-State-Tree": '["",{"children":["(chat)",{"children":["__PAGE__",{}, "/", "refresh"]}]},null,"refetch"]',
116
- "DNT": "1",
117
- "Sec-GPC": "1",
118
- "Connection": "keep-alive",
119
- "Sec-Fetch-Dest": "empty",
120
- "Sec-Fetch-Mode": "cors",
121
- "Sec-Fetch-Site": "same-origin",
122
- "Priority": "u=0",
123
- }
124
- params = {"_rsc": "l4cx"}
125
- async with httpx.AsyncClient(http2=True) as client:
126
- response = await client.get(f"{api_domain}/", params=params, cookies=cookies, headers=headers, timeout=30)
127
- chat_ids = re.findall(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}',
128
- response.text)
129
- return chat_ids[-1]
130
-
131
-
132
- # API key validation
133
- api_key_header = APIKeyHeader(name="Authorization", auto_error=False)
134
-
135
-
136
- async def verify_api_key(authorization: str = Depends(api_key_header)):
137
- if not authorization:
138
- logger.error("Missing Authorization header")
139
- raise HTTPException(status_code=401, detail="Missing Authorization header")
140
- api_key = authorization.replace("Bearer ", "").strip() if authorization.startswith("Bearer ") else authorization
141
- if api_key != API_KEY:
142
- logger.error(f"Invalid API key: {api_key}")
143
- raise HTTPException(status_code=401, detail="Invalid API key")
144
- return api_key
145
-
146
-
147
- # Request models
148
- class Message(BaseModel):
149
- role: Literal["system", "user", "assistant"]
150
- content: str
151
-
152
-
153
- class ChatCompletionRequest(BaseModel):
154
- model: str
155
- messages: List[Message]
156
- stream: Optional[bool] = False
157
- temperature: Optional[float] = 0.7
158
- top_p: Optional[float] = 1.0
159
- presence_penalty: Optional[float] = 0
160
- frequency_penalty: Optional[float] = 0
161
- max_tokens: Optional[int] = None
162
-
163
-
164
- # Response generation
165
- def generate_chunk(_id: str, created: int, model: str, content: str = "", finish_reason: Optional[str] = None):
166
- chunk = {
167
- "id": _id,
168
- "object": "chat.completion.chunk",
169
- "created": created,
170
- "model": model,
171
- "choices": [{"index": 0, "delta": {"content": content}, "finish_reason": finish_reason}]
172
- }
173
- return f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
174
-
175
-
176
- def truncate_messages(messages: List[Message], max_chars: int = MAX_CHARS) -> List[Message]:
177
- total_chars = sum(len(msg.content) for msg in messages)
178
- if total_chars <= max_chars:
179
- return messages
180
- truncated = []
181
- current_chars = 0
182
- for msg in reversed(messages):
183
- if current_chars + len(msg.content) <= max_chars:
184
- truncated.insert(0, msg)
185
- current_chars += len(msg.content)
186
- else:
187
- break
188
- logger.info(f"Truncated messages: original {total_chars}, truncated {current_chars}")
189
- return truncated
190
-
191
-
192
- async def stream_response(request: ChatCompletionRequest, device_id: str, chat_id: str, timestamp: int, sign: str):
193
- truncated_messages = truncate_messages(request.messages)
194
- messages = [{"role": msg.role, "content": msg.content} for msg in truncated_messages]
195
- payload = {
196
- "id": chat_id,
197
- "messages": messages,
198
- "selectedChatModel": model_to_config[request.model]["model"],
199
- "isDeepThinkingEnabled": True,
200
- "isWebSearchEnabled": model_to_config[request.model]["isWebSearchEnabled"],
201
- }
202
- headers = {
203
- "User-Agent": get_user_agent(device_id),
204
- "Accept": "*/*",
205
- "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
206
- "Referer": f"https://ai-chatbot.top/chat/{chat_id}",
207
- "Content-Type": "application/json",
208
- "currentTime": str(timestamp),
209
- "sign": sign,
210
- "Origin": "https://ai-chatbot.top",
211
- "DNT": "1",
212
- "Sec-GPC": "1",
213
- "Connection": "keep-alive",
214
- "Sec-Fetch-Dest": "empty",
215
- "Sec-Fetch-Mode": "cors",
216
- "Sec-Fetch-Site": "same-origin",
217
- "Priority": "u=0",
218
- }
219
- cookies = {
220
- '_ga_HVMZBNYJML': 'GS1.1.1742013194.1.1.1742013780.0.0.0',
221
- '_ga': 'GA1.1.1029622546.1742013195',
222
- }
223
- _id = f"chatcmpl-{uuid.uuid4().hex}"
224
- created = int(time.time())
225
- logger.info(f"[stream] Start: chat_id={chat_id}, UA={headers['User-Agent']}")
226
- try:
227
- # 首先发送一个空chunk,兼容SSE客户端
228
- yield generate_chunk(_id, created, request.model, "")
229
- async with httpx.AsyncClient(http2=True, timeout=900) as client:
230
- async with client.stream("POST", f"{api_domain}/api/chat", json=payload, headers=headers, cookies=cookies) as response:
231
- if response.status_code != 200:
232
- logger.error(f"[stream] API error: status_code={response.status_code}")
233
- yield generate_chunk(_id, created, request.model, f"Error: HTTP {response.status_code}", "stop")
234
- # 明确结束
235
- yield "data: [DONE]\n\n"
236
- return
237
- thinking = False
238
- content_parts = []
239
- async for line in response.aiter_lines():
240
- line = line.strip()
241
- if not line:
242
- continue
243
- if line.startswith("g:"):
244
- if not thinking:
245
- thinking = True
246
- content_parts.append("<think>")
247
- yield generate_chunk(_id, created, request.model, "<think>")
248
- content = line[2:].strip().replace('"', '').replace("\\n", "\n")
249
- content_parts.append(content)
250
- yield generate_chunk(_id, created, request.model, content)
251
- elif line.startswith("0:"):
252
- if thinking:
253
- thinking = False
254
- content_parts.append("</think>")
255
- yield generate_chunk(_id, created, request.model, "</think>")
256
- content = line[2:].strip().replace('"', '').replace("\\n", "\n")
257
- content_parts.append(content)
258
- yield generate_chunk(_id, created, request.model, content)
259
- if thinking:
260
- content_parts.append("</think>")
261
- yield generate_chunk(_id, created, request.model, "</think>")
262
- # 结束信号
263
- yield generate_chunk(_id, created, request.model, "", "stop")
264
- yield "data: [DONE]\n\n"
265
- logger.info(f"[stream] Completed: chat_id={chat_id}, content={''.join(content_parts)}")
266
- except Exception as e:
267
- logger.error(f"[stream] Exception: {e}")
268
- yield generate_chunk(_id, created, request.model, f"[流式异常] {str(e)}", "stop")
269
- yield "data: [DONE]\n\n"
270
-
271
-
272
- # not_stream_response
273
- async def not_stream_response(request: ChatCompletionRequest, device_id: str, chat_id: str, timestamp: int, sign: str):
274
- truncated_messages = truncate_messages(request.messages) if callable(truncate_messages) else truncate_messages(request.messages)
275
- print(truncated_messages)
276
- messages = [{"role": msg.role, "content": msg.content} for msg in truncated_messages]
277
- payload = {
278
- "id": chat_id,
279
- "messages": messages,
280
- "selectedChatModel": model_to_config[request.model]["model"],
281
- "isDeepThinkingEnabled": True,
282
- "isWebSearchEnabled": model_to_config[request.model]["isWebSearchEnabled"],
283
- }
284
- headers = {
285
- "User-Agent": get_user_agent(device_id),
286
- "Accept": "*/*",
287
- "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
288
- "Referer": f"https://ai-chatbot.top/chat/{chat_id}",
289
- "Content-Type": "application/json",
290
- "currentTime": str(timestamp),
291
- "sign": sign,
292
- "Origin": "https://ai-chatbot.top",
293
- "DNT": "1",
294
- "Sec-GPC": "1",
295
- "Connection": "keep-alive",
296
- "Sec-Fetch-Dest": "empty",
297
- "Sec-Fetch-Mode": "cors",
298
- "Sec-Fetch-Site": "same-origin",
299
- "Priority": "u=0",
300
- }
301
- cookies = {
302
- '_ga_HVMZBNYJML': 'GS1.1.1742013194.1.1.1742013780.0.0.0',
303
- '_ga': 'GA1.1.1029622546.1742013195',
304
- }
305
- _id = f"chatcmpl-{uuid.uuid4().hex}"
306
- created = int(time.time())
307
- logger.info(f"Starting non-stream response: chat_id={chat_id}, UA={headers['User-Agent']}")
308
-
309
- async with httpx.AsyncClient(http2=True, timeout=900) as client:
310
- response = await client.post(f"{api_domain}/api/chat", json=payload, headers=headers, cookies=cookies)
311
-
312
- if response.status_code != 200:
313
- logger.error(f"API error: status_code={response.status_code}")
314
- return {"error": {"message": f"Error: HTTP {response.status_code}", "type": "invalid_request_error"}}
315
- else:
316
- contents = ''
317
- think_content = ''
318
- print(response.text)
319
- for line in response.iter_lines():
320
- line = line.strip()
321
- if not line:
322
- continue
323
- if line.startswith("g:"):
324
- content = line[3:-1].replace('\\n', '\n')
325
- think_content += content
326
- continue
327
- elif line.startswith("0:"):
328
- if think_content:
329
- contents = '<think>' + think_content + '</think>'
330
- think_content = ''
331
- content = line[3:-1].replace('\\n', '\n')
332
- contents += content
333
- continue
334
- elif line.startswith("e:"):
335
- continue
336
- elif line.startswith("d:"):
337
- continue
338
- else:
339
- continue
340
- print({
341
- "id": _id,
342
- "object": "chat.completion",
343
- "created": created,
344
- "model": request.model,
345
- "choices": [{"index": 0, "message": {"role": "assistant", "content": contents}, "finish_reason": None}],
346
- })
347
- return {
348
- "id": _id,
349
- "object": "chat.completion",
350
- "created": created,
351
- "model": request.model,
352
- "choices": [{"index": 0, "message": {"role": "assistant", "content": contents}, "finish_reason": None}],
353
- }
354
-
355
-
356
- # Endpoints
357
- @app.post("/v1/chat/completions")
358
- async def chat_completions(request: ChatCompletionRequest, _: str = Depends(verify_api_key)):
359
- if request.model not in supported_models:
360
- request.model = "DeepSeek-R1"
361
- device_id = generate_device_id()
362
- chat_id = await get_chat_id()
363
- timestamp = int(time.time() * 1000)
364
- sign = generate_sign(chat_id, timestamp)
365
- logger.info(f"Chat request: model={request.model}, stream={request.stream}, chat_id={chat_id}")
366
-
367
- if request.stream:
368
- return StreamingResponse(stream_response(request, device_id, chat_id, timestamp, sign),
369
- media_type="text/event-stream")
370
- else:
371
- # not_stream_response(request, device_id, chat_id, timestamp, sign)
372
- return await not_stream_response(request, device_id, chat_id, timestamp, sign)
373
-
374
-
375
- @app.get("/v1/models")
376
- async def list_models(_: str = Depends(verify_api_key)):
377
- current_time = int(time.time())
378
- models = [
379
- {
380
- "id": model,
381
- "object": "model",
382
- "created": current_time,
383
- "owned_by": "aichatbot",
384
- } for model in supported_models
385
- ]
386
- logger.info("Returning model list")
387
- return {"object": "list", "data": models}
388
-
389
-
390
- @app.get("/health")
391
- async def health_check():
392
- chat_id = await get_chat_id()
393
- return {"status": "ok", "session": "active" if chat_id else "inactive"}
394
-
395
-
396
- if __name__ == "__main__":
397
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
go.mod ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module aichatbot2api
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/gin-gonic/gin v1.9.1
7
+ github.com/google/uuid v1.3.1
8
+ github.com/joho/godotenv v1.5.1
9
+ )
10
+
11
+ require (
12
+ github.com/bytedance/sonic v1.9.1 // indirect
13
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
14
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
15
+ github.com/gin-contrib/sse v0.1.0 // indirect
16
+ github.com/go-playground/locales v0.14.1 // indirect
17
+ github.com/go-playground/universal-translator v0.18.1 // indirect
18
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
19
+ github.com/goccy/go-json v0.10.2 // indirect
20
+ github.com/json-iterator/go v1.1.12 // indirect
21
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
22
+ github.com/leodido/go-urn v1.2.4 // indirect
23
+ github.com/mattn/go-isatty v0.0.19 // indirect
24
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
25
+ github.com/modern-go/reflect2 v1.0.2 // indirect
26
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
27
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
28
+ github.com/ugorji/go/codec v1.2.11 // indirect
29
+ golang.org/x/arch v0.3.0 // indirect
30
+ golang.org/x/crypto v0.9.0 // indirect
31
+ golang.org/x/net v0.10.0 // indirect
32
+ golang.org/x/sys v0.8.0 // indirect
33
+ golang.org/x/text v0.9.0 // indirect
34
+ google.golang.org/protobuf v1.30.0 // indirect
35
+ gopkg.in/yaml.v3 v3.0.1 // indirect
36
+ )
go.sum ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
2
+ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
3
+ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
4
+ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
5
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
6
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
7
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10
+ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
11
+ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
12
+ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
13
+ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
14
+ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
15
+ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
16
+ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
17
+ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
18
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
19
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
20
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
21
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
22
+ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
23
+ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
24
+ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
25
+ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
26
+ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
27
+ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
28
+ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
29
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
30
+ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
31
+ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
32
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
33
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
34
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
35
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
36
+ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
37
+ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
38
+ github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
39
+ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
40
+ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
41
+ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
42
+ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
43
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
44
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
45
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
46
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
47
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
48
+ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
49
+ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
50
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
51
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
52
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
53
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
54
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
55
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
56
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
57
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
58
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
59
+ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
60
+ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
61
+ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
62
+ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
63
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
64
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
65
+ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
66
+ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
67
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
68
+ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
69
+ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
70
+ golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
71
+ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
72
+ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
73
+ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
74
+ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
75
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76
+ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
77
+ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78
+ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
79
+ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
80
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
81
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
82
+ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
83
+ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
84
+ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
85
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
86
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
87
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
88
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
89
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
90
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
main.go ADDED
@@ -0,0 +1,750 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "crypto/md5"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io"
9
+ "log"
10
+ "math/rand"
11
+ "net"
12
+ "net/http"
13
+ "os"
14
+ "regexp"
15
+ "strings"
16
+ "sync"
17
+ "time"
18
+
19
+ "github.com/gin-gonic/gin"
20
+ "github.com/google/uuid"
21
+ "github.com/joho/godotenv"
22
+ )
23
+
24
+ // Message 消息结构体,表示对话中的一条消息
25
+ type Message struct {
26
+ Role string `json:"role" binding:"required,oneof=system user assistant"`
27
+ Content string `json:"content" binding:"required"`
28
+ }
29
+
30
+ // ChatCompletionRequest 聊天完成请求结构体
31
+ type ChatCompletionRequest struct {
32
+ Model string `json:"model"`
33
+ Messages []Message `json:"messages"`
34
+ Stream bool `json:"stream"`
35
+ Temperature float64 `json:"temperature"`
36
+ TopP float64 `json:"top_p"`
37
+ PresencePenalty float64 `json:"presence_penalty"`
38
+ FrequencyPenalty float64 `json:"frequency_penalty"`
39
+ MaxTokens int `json:"max_tokens"`
40
+ }
41
+
42
+ // Choice OpenAI 格式的选择结构体
43
+ type Choice struct {
44
+ Index int `json:"index"`
45
+ Delta *Delta `json:"delta,omitempty"`
46
+ Message *Message `json:"message,omitempty"`
47
+ FinishReason *string `json:"finish_reason"`
48
+ }
49
+
50
+ // Delta 流式响应中的增量内容
51
+ type Delta struct {
52
+ Role string `json:"role,omitempty"`
53
+ Content string `json:"content,omitempty"`
54
+ }
55
+
56
+ // Usage 令牌使用统计
57
+ type Usage struct {
58
+ PromptTokens int `json:"prompt_tokens"`
59
+ CompletionTokens int `json:"completion_tokens"`
60
+ TotalTokens int `json:"total_tokens"`
61
+ }
62
+
63
+ // ChatCompletionResponse OpenAI 格式的聊天响应
64
+ type ChatCompletionResponse struct {
65
+ ID string `json:"id"`
66
+ Object string `json:"object"`
67
+ Created int64 `json:"created"`
68
+ Model string `json:"model"`
69
+ Choices []Choice `json:"choices"`
70
+ Usage *Usage `json:"usage,omitempty"`
71
+ }
72
+
73
+ // ModelInfo 模型信息结构体
74
+ type ModelInfo struct {
75
+ ID string `json:"id"`
76
+ Object string `json:"object"`
77
+ Created int64 `json:"created"`
78
+ OwnedBy string `json:"owned_by"`
79
+ }
80
+
81
+ // ModelList 模型列表结构体
82
+ type ModelList struct {
83
+ Object string `json:"object"`
84
+ Data []ModelInfo `json:"data"`
85
+ }
86
+
87
+ var (
88
+ apiKey string
89
+ enableCORS bool
90
+ randomUA bool
91
+ supportedModels = []string{"DeepSeek-R1", "DeepSeek-V3"}
92
+ modelToConfig = map[string]map[string]interface{}{
93
+ "DeepSeek-R1": {"model": "deepseek-huoshan", "isWebSearchEnabled": false},
94
+ "DeepSeek-V3": {"model": "deepseek-guiji", "isWebSearchEnabled": false},
95
+ }
96
+ apiDomain = "https://ai-chatbot.top"
97
+ defaultUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0"
98
+ deviceUAMap = make(map[string]string)
99
+ deviceUAMutex sync.Mutex
100
+
101
+ // 随机User-Agent列表
102
+ userAgents = []string{
103
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
104
+ "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",
105
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
106
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
107
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
108
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0",
109
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0",
110
+ }
111
+
112
+ // 全局 HTTP 客户端配置,支持连接池和 HTTP/2
113
+ httpClient *http.Client
114
+
115
+ // chatID 缓存系统
116
+ chatIDCache struct {
117
+ sync.RWMutex
118
+ value string
119
+ expiresAt time.Time
120
+ }
121
+
122
+ // chatID 获取的并发控制
123
+ chatIDFetching sync.Mutex
124
+ )
125
+
126
+ // initHTTPClient 初始化优化的 HTTP 客户端,支持连接池和长连接
127
+ func initHTTPClient() {
128
+ transport := &http.Transport{
129
+ DialContext: (&net.Dialer{
130
+ Timeout: 10 * time.Second,
131
+ KeepAlive: 30 * time.Second,
132
+ }).DialContext,
133
+ ForceAttemptHTTP2: true,
134
+ MaxIdleConns: 100,
135
+ MaxIdleConnsPerHost: 10,
136
+ IdleConnTimeout: 90 * time.Second,
137
+ TLSHandshakeTimeout: 10 * time.Second,
138
+ ExpectContinueTimeout: 1 * time.Second,
139
+ DisableKeepAlives: false,
140
+ }
141
+
142
+ httpClient = &http.Client{
143
+ Transport: transport,
144
+ Timeout: 10 * time.Minute, // 10分钟超时支持长时间思考
145
+ }
146
+ }
147
+
148
+ // nanoid 生成指定长度的随机字符串
149
+ func nanoid(size int) string {
150
+ alphabet := "abcdefgh0ijkl1mno2pqrs3tuv4wxyz5ABCDEFGH6IJKL7MNO8PQRS9TUV-WXYZ_"
151
+ b := make([]byte, size)
152
+ for i := range b {
153
+ b[i] = alphabet[rand.Intn(len(alphabet))]
154
+ }
155
+ return string(b)
156
+ }
157
+
158
+ // generateDeviceID 生成设备ID
159
+ func generateDeviceID() string {
160
+ return fmt.Sprintf("%s_%s", uuid.New().String(), nanoid(20))
161
+ }
162
+
163
+ // getRandomUserAgent 获取随机User-Agent
164
+ func getRandomUserAgent() string {
165
+ return userAgents[rand.Intn(len(userAgents))]
166
+ }
167
+
168
+ // getUserAgent 获取或生成用户代理字符串
169
+ func getUserAgent(deviceID string) string {
170
+ if !randomUA {
171
+ return defaultUA
172
+ }
173
+
174
+ deviceUAMutex.Lock()
175
+ defer deviceUAMutex.Unlock()
176
+ if ua, ok := deviceUAMap[deviceID]; ok {
177
+ return ua
178
+ }
179
+ // 生成随机用户代理
180
+ randomUserAgent := getRandomUserAgent()
181
+ deviceUAMap[deviceID] = randomUserAgent
182
+ return randomUserAgent
183
+ }
184
+
185
+ // generateSign 生成签名用于API认证
186
+ func generateSign(chatID string, timestamp int64) string {
187
+ msg := fmt.Sprintf("%s%d@!~chatbot.0868", chatID, timestamp)
188
+ hash := md5.Sum([]byte(msg))
189
+ return fmt.Sprintf("%x", hash)
190
+ }
191
+
192
+ // getChatID 获取聊天ID,支持缓存和并发控制
193
+ func getChatID() (string, error) {
194
+ // 读取缓存
195
+ chatIDCache.RLock()
196
+ if time.Now().Before(chatIDCache.expiresAt) && chatIDCache.value != "" {
197
+ cachedID := chatIDCache.value
198
+ chatIDCache.RUnlock()
199
+ return cachedID, nil
200
+ }
201
+ chatIDCache.RUnlock()
202
+
203
+ // 使用互斥锁避免重复请求
204
+ chatIDFetching.Lock()
205
+ defer chatIDFetching.Unlock()
206
+
207
+ // 双重检查,可能在等待锁的过程中其他 goroutine 已经更新了缓存
208
+ chatIDCache.RLock()
209
+ if time.Now().Before(chatIDCache.expiresAt) && chatIDCache.value != "" {
210
+ cachedID := chatIDCache.value
211
+ chatIDCache.RUnlock()
212
+ return cachedID, nil
213
+ }
214
+ chatIDCache.RUnlock()
215
+
216
+ // 使用全局 HTTP 客户端获取新的 chatID
217
+ req, _ := http.NewRequest("GET", apiDomain+"/", nil)
218
+ req.Header.Set("User-Agent", defaultUA)
219
+ req.Header.Set("Accept", "*/*")
220
+ req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
221
+ req.Header.Set("Referer", "https://ai-chatbot.top/")
222
+
223
+ resp, err := httpClient.Do(req)
224
+ if err != nil {
225
+ return "", err
226
+ }
227
+ defer resp.Body.Close()
228
+
229
+ body, _ := io.ReadAll(resp.Body)
230
+ reg := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}`)
231
+ ids := reg.FindAllString(string(body), -1)
232
+ if len(ids) == 0 {
233
+ return "", fmt.Errorf("no chat id found")
234
+ }
235
+
236
+ chatID := ids[len(ids)-1]
237
+
238
+ // 更新缓存
239
+ chatIDCache.Lock()
240
+ chatIDCache.value = chatID
241
+ chatIDCache.expiresAt = time.Now().Add(5 * time.Minute)
242
+ chatIDCache.Unlock()
243
+
244
+ return chatID, nil
245
+ }
246
+
247
+ // preloadChatID 异步预取 chatID,在缓存过期前自动刷新
248
+ func preloadChatID() {
249
+ go func() {
250
+ for {
251
+ // 在缓存过期前 1 分钟刷新
252
+ sleepDuration := 4 * time.Minute
253
+ if !chatIDCache.expiresAt.IsZero() {
254
+ timeToExpiry := time.Until(chatIDCache.expiresAt)
255
+ if timeToExpiry > time.Minute {
256
+ sleepDuration = timeToExpiry - time.Minute
257
+ }
258
+ }
259
+
260
+ time.Sleep(sleepDuration)
261
+ getChatID() // 刷新缓存
262
+ }
263
+ }()
264
+ }
265
+
266
+ // ChatbotFinish ai-chatbot.top 的结束信息结构
267
+ type ChatbotFinish struct {
268
+ FinishReason string `json:"finishReason"`
269
+ Usage Usage `json:"usage"`
270
+ IsContinued bool `json:"isContinued"`
271
+ }
272
+
273
+ // parseChatbotLine 解析 ai-chatbot.top 响应的单行内容
274
+ func parseChatbotLine(line string) (string, string, error) {
275
+ if len(line) < 2 {
276
+ return "", "", fmt.Errorf("line too short")
277
+ }
278
+
279
+ colonIndex := strings.Index(line, ":")
280
+ if colonIndex == -1 {
281
+ return "", "", fmt.Errorf("no colon found")
282
+ }
283
+
284
+ prefix := line[:colonIndex]
285
+ content := line[colonIndex+1:]
286
+
287
+ return prefix, content, nil
288
+ }
289
+
290
+ // handleStreamResponse 处理流式响应并转换为 OpenAI 格式
291
+ func handleStreamResponse(resp *http.Response, c *gin.Context, model string) {
292
+ // 设置 SSE 响应头
293
+ c.Header("Content-Type", "text/event-stream")
294
+ c.Header("Cache-Control", "no-cache")
295
+ c.Header("Connection", "keep-alive")
296
+
297
+ scanner := bufio.NewScanner(resp.Body)
298
+ created := time.Now().Unix()
299
+ chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29))
300
+ thinking := false
301
+
302
+ // 首先发送一个空chunk,兼容SSE客户端
303
+ chunk := ChatCompletionResponse{
304
+ ID: chatID,
305
+ Object: "chat.completion.chunk",
306
+ Created: created,
307
+ Model: model,
308
+ Choices: []Choice{{
309
+ Index: 0,
310
+ Delta: &Delta{Content: ""},
311
+ FinishReason: nil,
312
+ }},
313
+ }
314
+ jsonData, _ := json.Marshal(chunk)
315
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
316
+ c.Writer.(http.Flusher).Flush()
317
+
318
+ for scanner.Scan() {
319
+ line := strings.TrimSpace(scanner.Text())
320
+ if line == "" {
321
+ continue
322
+ }
323
+
324
+ prefix, content, err := parseChatbotLine(line)
325
+ if err != nil {
326
+ continue
327
+ }
328
+
329
+ switch prefix {
330
+ case "f":
331
+ // 忽略 messageId,OpenAI 不需要这个
332
+ continue
333
+ case "g":
334
+ // 思考过程内容
335
+ if !thinking {
336
+ thinking = true
337
+ // 发送 <think> 开始标记
338
+ chunk := ChatCompletionResponse{
339
+ ID: chatID,
340
+ Object: "chat.completion.chunk",
341
+ Created: created,
342
+ Model: model,
343
+ Choices: []Choice{{
344
+ Index: 0,
345
+ Delta: &Delta{Content: "<think>"},
346
+ FinishReason: nil,
347
+ }},
348
+ }
349
+ jsonData, _ := json.Marshal(chunk)
350
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
351
+ c.Writer.(http.Flusher).Flush()
352
+ }
353
+
354
+ var contentStr string
355
+ if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
356
+ continue
357
+ }
358
+ contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
359
+
360
+ chunk := ChatCompletionResponse{
361
+ ID: chatID,
362
+ Object: "chat.completion.chunk",
363
+ Created: created,
364
+ Model: model,
365
+ Choices: []Choice{{
366
+ Index: 0,
367
+ Delta: &Delta{Content: contentStr},
368
+ FinishReason: nil,
369
+ }},
370
+ }
371
+
372
+ jsonData, _ := json.Marshal(chunk)
373
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
374
+ c.Writer.(http.Flusher).Flush()
375
+
376
+ case "0":
377
+ // 最终回答内容
378
+ if thinking {
379
+ thinking = false
380
+ // 发送 </think> 结束标记
381
+ chunk := ChatCompletionResponse{
382
+ ID: chatID,
383
+ Object: "chat.completion.chunk",
384
+ Created: created,
385
+ Model: model,
386
+ Choices: []Choice{{
387
+ Index: 0,
388
+ Delta: &Delta{Content: "</think>"},
389
+ FinishReason: nil,
390
+ }},
391
+ }
392
+ jsonData, _ := json.Marshal(chunk)
393
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
394
+ c.Writer.(http.Flusher).Flush()
395
+ }
396
+
397
+ var contentStr string
398
+ if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
399
+ continue
400
+ }
401
+ contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
402
+
403
+ chunk := ChatCompletionResponse{
404
+ ID: chatID,
405
+ Object: "chat.completion.chunk",
406
+ Created: created,
407
+ Model: model,
408
+ Choices: []Choice{{
409
+ Index: 0,
410
+ Delta: &Delta{Content: contentStr},
411
+ FinishReason: nil,
412
+ }},
413
+ }
414
+
415
+ jsonData, _ := json.Marshal(chunk)
416
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
417
+ c.Writer.(http.Flusher).Flush()
418
+
419
+ case "e", "d":
420
+ // 结束信息
421
+ if thinking {
422
+ // 如果还在思考模式,发送结束标记
423
+ chunk := ChatCompletionResponse{
424
+ ID: chatID,
425
+ Object: "chat.completion.chunk",
426
+ Created: created,
427
+ Model: model,
428
+ Choices: []Choice{{
429
+ Index: 0,
430
+ Delta: &Delta{Content: "</think>"},
431
+ FinishReason: nil,
432
+ }},
433
+ }
434
+ jsonData, _ := json.Marshal(chunk)
435
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
436
+ c.Writer.(http.Flusher).Flush()
437
+ }
438
+
439
+ var finishInfo ChatbotFinish
440
+ if err := json.Unmarshal([]byte(content), &finishInfo); err != nil {
441
+ continue
442
+ }
443
+
444
+ finishReason := finishInfo.FinishReason
445
+ chunk := ChatCompletionResponse{
446
+ ID: chatID,
447
+ Object: "chat.completion.chunk",
448
+ Created: created,
449
+ Model: model,
450
+ Choices: []Choice{{
451
+ Index: 0,
452
+ Delta: &Delta{},
453
+ FinishReason: &finishReason,
454
+ }},
455
+ Usage: &Usage{
456
+ PromptTokens: finishInfo.Usage.PromptTokens,
457
+ CompletionTokens: finishInfo.Usage.CompletionTokens,
458
+ TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens,
459
+ },
460
+ }
461
+
462
+ jsonData, _ := json.Marshal(chunk)
463
+ fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
464
+ c.Writer.(http.Flusher).Flush()
465
+
466
+ // 发送 [DONE] 信号
467
+ fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
468
+ c.Writer.(http.Flusher).Flush()
469
+ return
470
+ }
471
+ }
472
+ }
473
+
474
+ // handleNonStreamResponse 处理非流式响应并转换为 OpenAI 格式
475
+ func handleNonStreamResponse(resp *http.Response, c *gin.Context, model string) {
476
+ scanner := bufio.NewScanner(resp.Body)
477
+ created := time.Now().Unix()
478
+ chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29))
479
+
480
+ var fullContent strings.Builder
481
+ var thinkContent strings.Builder
482
+ var usage *Usage
483
+
484
+ for scanner.Scan() {
485
+ line := strings.TrimSpace(scanner.Text())
486
+ if line == "" {
487
+ continue
488
+ }
489
+
490
+ prefix, content, err := parseChatbotLine(line)
491
+ if err != nil {
492
+ continue
493
+ }
494
+
495
+ switch prefix {
496
+ case "g":
497
+ // 思考过程内容
498
+ var contentStr string
499
+ if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
500
+ continue
501
+ }
502
+ contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
503
+ thinkContent.WriteString(contentStr)
504
+
505
+ case "0":
506
+ // 最终回答内容
507
+ if thinkContent.Len() > 0 {
508
+ fullContent.WriteString("<think>")
509
+ fullContent.WriteString(thinkContent.String())
510
+ fullContent.WriteString("</think>")
511
+ thinkContent.Reset()
512
+ }
513
+
514
+ var contentStr string
515
+ if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
516
+ continue
517
+ }
518
+ contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
519
+ fullContent.WriteString(contentStr)
520
+
521
+ case "e", "d":
522
+ // 结束信息
523
+ var finishInfo ChatbotFinish
524
+ if err := json.Unmarshal([]byte(content), &finishInfo); err != nil {
525
+ continue
526
+ }
527
+
528
+ usage = &Usage{
529
+ PromptTokens: finishInfo.Usage.PromptTokens,
530
+ CompletionTokens: finishInfo.Usage.CompletionTokens,
531
+ TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens,
532
+ }
533
+ break
534
+ }
535
+ }
536
+
537
+ // 如果还有未处理的思考内容,添加到最终内容中
538
+ if thinkContent.Len() > 0 {
539
+ fullContent.WriteString("<think>")
540
+ fullContent.WriteString(thinkContent.String())
541
+ fullContent.WriteString("</think>")
542
+ }
543
+
544
+ response := ChatCompletionResponse{
545
+ ID: chatID,
546
+ Object: "chat.completion",
547
+ Created: created,
548
+ Model: model,
549
+ Choices: []Choice{{
550
+ Index: 0,
551
+ Message: &Message{
552
+ Role: "assistant",
553
+ Content: fullContent.String(),
554
+ },
555
+ FinishReason: func() *string { s := "stop"; return &s }(),
556
+ }},
557
+ Usage: usage,
558
+ }
559
+
560
+ c.JSON(http.StatusOK, response)
561
+ }
562
+
563
+ // verifyAPIKey API密钥验证中间件
564
+ func verifyAPIKey(c *gin.Context) {
565
+ key := c.GetHeader("Authorization")
566
+ fmt.Printf("[DEBUG] Authorization header: '%s'\n", key)
567
+ fmt.Printf("[DEBUG] apiKey: '%s'\n", apiKey)
568
+
569
+ if key == "" {
570
+ c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Missing Authorization header"})
571
+ return
572
+ }
573
+
574
+ if strings.HasPrefix(key, "Bearer ") {
575
+ key = strings.TrimSpace(strings.TrimPrefix(key, "Bearer "))
576
+ }
577
+
578
+ fmt.Printf("[DEBUG] Processed key: '%s'\n", key)
579
+
580
+ if key != apiKey {
581
+ fmt.Printf("[DEBUG] Key comparison failed: '%s' != '%s'\n", key, apiKey)
582
+ c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Invalid API key"})
583
+ return
584
+ }
585
+
586
+ fmt.Println("[DEBUG] API key verification passed!")
587
+ c.Next()
588
+ }
589
+
590
+ // main 主函数,初始化并启动服务器
591
+ func main() {
592
+ _ = godotenv.Load()
593
+ apiKey = os.Getenv("API_KEY")
594
+ fmt.Println("[DEBUG] Loaded API_KEY:", apiKey)
595
+ if apiKey == "" {
596
+ log.Fatal("API_KEY not found in .env file")
597
+ }
598
+
599
+ // 读取RANDOM_UA环境变量
600
+ randomUAStr := strings.ToLower(os.Getenv("RANDOM_UA"))
601
+ randomUA = randomUAStr == "true" || randomUAStr == "1" || randomUAStr == "yes"
602
+ fmt.Println("[DEBUG] RANDOM_UA enabled:", randomUA)
603
+
604
+ // 初始化优化的 HTTP 客户端
605
+ initHTTPClient()
606
+
607
+ // 启动异步 chatID 预加载
608
+ preloadChatID()
609
+
610
+ enableCORS = true
611
+ gin.SetMode(gin.ReleaseMode)
612
+ r := gin.Default()
613
+
614
+ // CORS 中间件配置
615
+ if enableCORS {
616
+ r.Use(func(c *gin.Context) {
617
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
618
+ c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
619
+ c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
620
+ if c.Request.Method == "OPTIONS" {
621
+ c.AbortWithStatus(204)
622
+ return
623
+ }
624
+ c.Next()
625
+ })
626
+ }
627
+
628
+ // 健康检查端点不需要 API 验证
629
+ r.GET("/health", func(c *gin.Context) {
630
+ chatID, err := getChatID()
631
+ status := "inactive"
632
+ if err == nil && chatID != "" {
633
+ status = "active"
634
+ }
635
+ c.JSON(http.StatusOK, gin.H{"status": "ok", "session": status})
636
+ })
637
+
638
+ // 需要 API 验证的端点组
639
+ authorized := r.Group("/")
640
+ authorized.Use(verifyAPIKey)
641
+
642
+ // 聊天完成接口
643
+ authorized.POST("/v1/chat/completions", func(c *gin.Context) {
644
+ var req ChatCompletionRequest
645
+ if err := c.ShouldBindJSON(&req); err != nil {
646
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
647
+ return
648
+ }
649
+ if !contains(supportedModels, req.Model) {
650
+ req.Model = "DeepSeek-R1"
651
+ }
652
+ deviceID := generateDeviceID()
653
+ chatID, err := getChatID()
654
+ if err != nil {
655
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get chat id"})
656
+ return
657
+ }
658
+ timestamp := time.Now().UnixNano() / 1e6
659
+ sign := generateSign(chatID, timestamp)
660
+
661
+ // 构造请求载荷
662
+ messages := []map[string]string{}
663
+ for _, m := range req.Messages {
664
+ messages = append(messages, map[string]string{"role": m.Role, "content": m.Content})
665
+ }
666
+ payload := map[string]interface{}{
667
+ "id": chatID,
668
+ "messages": messages,
669
+ "selectedChatModel": modelToConfig[req.Model]["model"],
670
+ "isDeepThinkingEnabled": true,
671
+ "isWebSearchEnabled": modelToConfig[req.Model]["isWebSearchEnabled"],
672
+ }
673
+
674
+ // 构造请求头
675
+ headers := map[string]string{
676
+ "User-Agent": getUserAgent(deviceID),
677
+ "Accept": "*/*",
678
+ "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
679
+ "Referer": "https://ai-chatbot.top/chat/" + chatID,
680
+ "Content-Type": "application/json",
681
+ "currentTime": fmt.Sprintf("%d", timestamp),
682
+ "sign": sign,
683
+ "Origin": "https://ai-chatbot.top",
684
+ "DNT": "1",
685
+ "Sec-GPC": "1",
686
+ "Connection": "keep-alive",
687
+ "Sec-Fetch-Dest": "empty",
688
+ "Sec-Fetch-Mode": "cors",
689
+ "Sec-Fetch-Site": "same-origin",
690
+ "Priority": "u=0",
691
+ }
692
+
693
+ // 构造cookies
694
+ cookies := []*http.Cookie{
695
+ {Name: "_ga_HVMZBNYJML", Value: "GS1.1.1742013194.1.1.1742013780.0.0.0"},
696
+ {Name: "_ga", Value: "GA1.1.1029622546.1742013195"},
697
+ }
698
+
699
+ // 使用全局 HTTP 客户端发送请求
700
+ jsonBytes, _ := json.Marshal(payload)
701
+ req2, _ := http.NewRequest("POST", apiDomain+"/api/chat", strings.NewReader(string(jsonBytes)))
702
+ for k, v := range headers {
703
+ req2.Header.Set(k, v)
704
+ }
705
+ for _, ck := range cookies {
706
+ req2.AddCookie(ck)
707
+ }
708
+
709
+ resp, err := httpClient.Do(req2)
710
+ if err != nil {
711
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to call ai-chatbot.top"})
712
+ return
713
+ }
714
+ defer resp.Body.Close()
715
+
716
+ // 根据 stream 参数选择处理方式
717
+ if req.Stream {
718
+ handleStreamResponse(resp, c, req.Model)
719
+ } else {
720
+ handleNonStreamResponse(resp, c, req.Model)
721
+ }
722
+ })
723
+
724
+ // 模型列表接口
725
+ authorized.GET("/v1/models", func(c *gin.Context) {
726
+ currentTime := time.Now().Unix()
727
+ models := []ModelInfo{}
728
+ for _, m := range supportedModels {
729
+ models = append(models, ModelInfo{
730
+ ID: m,
731
+ Object: "model",
732
+ Created: currentTime,
733
+ OwnedBy: "aichatbot",
734
+ })
735
+ }
736
+ c.JSON(http.StatusOK, ModelList{Object: "list", Data: models})
737
+ })
738
+
739
+ r.Run(":7860")
740
+ }
741
+
742
+ // contains 检查字符串数组中是否包含指定字符串
743
+ func contains(arr []string, s string) bool {
744
+ for _, v := range arr {
745
+ if v == s {
746
+ return true
747
+ }
748
+ }
749
+ return false
750
+ }
main.ts DELETED
@@ -1,679 +0,0 @@
1
- import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
2
- import { Md5 } from "https://deno.land/std@0.140.0/hash/md5.ts";
3
-
4
- const API_DOMAIN = 'https://ai-api.dangbei.net';
5
- const USER_AGENTS = [
6
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
7
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
8
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
9
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
10
- 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
11
- ];
12
- const VALID_API_KEY = Deno.env.get('VALID_API_KEY');
13
- const MAX_CONVERSATIONS_PER_DEVICE = 10; // 每个设备最多创建的会话数
14
-
15
- class ChatManage {
16
- private currentDeviceId: string | null = null;
17
- private currentConversationId: string | null = null;
18
- private conversationCount = 0;
19
- private currentUserAgent: string;
20
-
21
- constructor() {
22
- this.currentUserAgent = this.getRandomUserAgent();
23
- }
24
-
25
- private getRandomUserAgent(): string {
26
- return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
27
- }
28
-
29
- getOrCreateIds(forceNew = false) {
30
- let newDeviceId = this.currentDeviceId;
31
- let newConversationId = this.currentConversationId;
32
-
33
- if (forceNew || !newDeviceId || this.conversationCount >= MAX_CONVERSATIONS_PER_DEVICE) {
34
- newDeviceId = this.generateDeviceId();
35
- newConversationId = null;
36
- this.conversationCount = 0;
37
- // 在生成新设备ID时更新 User-Agent
38
- this.currentUserAgent = this.getRandomUserAgent();
39
- }
40
-
41
- this.currentDeviceId = newDeviceId;
42
- this.currentConversationId = newConversationId;
43
-
44
- return {
45
- deviceId: newDeviceId,
46
- conversationId: newConversationId,
47
- userAgent: this.currentUserAgent
48
- };
49
- }
50
-
51
- updateConversationId(conversationId: string) {
52
- this.currentConversationId = conversationId;
53
- this.conversationCount++;
54
- }
55
-
56
- generateDeviceId() {
57
- const uuid = crypto.randomUUID();
58
- const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
59
- const nanoid = Array.from(crypto.getRandomValues(new Uint8Array(20)))
60
- .map(b => urlAlphabet[b % urlAlphabet.length])
61
- .join('');
62
- return `${uuid.replace(/-/g, '')}_${nanoid}`;
63
- }
64
- }
65
-
66
- class Pipe {
67
- private dataPrefix = 'data:';
68
- private chatManage = new ChatManage();
69
- private searchModels: Record<string, string> = {
70
- 'DeepSeek-R1-Search': 'deepseek',
71
- 'DeepSeek-V3-Search': 'deepseek',
72
- 'Doubao-Search': 'doubao',
73
- 'Qwen-Search': 'qwen'
74
- };
75
-
76
- // 创建新的会话
77
- async _create_conversation(deviceId: string) {
78
- const { userAgent } = this.chatManage.getOrCreateIds(false);
79
- const payload = { botCode: "AI_SEARCH" };
80
- const timestamp = Math.floor(Date.now() / 1000).toString();
81
- const nonce = this.nanoid(21);
82
- const sign = await this.generateSign(timestamp, payload, nonce);
83
-
84
- const headers = {
85
- "Origin": "https://ai.dangbei.com",
86
- "Referer": "https://ai.dangbei.com/",
87
- "User-Agent": userAgent,
88
- "deviceId": deviceId,
89
- "nonce": nonce,
90
- "sign": sign,
91
- "timestamp": timestamp,
92
- "Content-Type": "application/json"
93
- };
94
-
95
- try {
96
- console.log('Creating conversation with:', {
97
- url: `${API_DOMAIN}/ai-search/conversationApi/v1/create`,
98
- headers,
99
- payload
100
- });
101
-
102
- const response = await fetch(`${API_DOMAIN}/ai-search/conversationApi/v1/create`, {
103
- method: 'POST',
104
- headers,
105
- body: JSON.stringify(payload),
106
- });
107
-
108
- console.log('Response status:', response.status);
109
- const responseText = await response.text();
110
- console.log('Response body:', responseText);
111
-
112
- if (response.ok) {
113
- try {
114
- const data = JSON.parse(responseText);
115
- if (data.success) {
116
- console.log('Successfully created conversation:', data.data.conversationId);
117
- return data.data.conversationId;
118
- } else {
119
- console.error('API returned success: false:', data);
120
- }
121
- } catch (e) {
122
- console.error('Failed to parse response:', e);
123
- }
124
- } else {
125
- console.error('HTTP error:', response.status, responseText);
126
- }
127
- } catch (e) {
128
- console.error('Error creating conversation:', e);
129
- }
130
- return null;
131
- }
132
-
133
- // 新增方法:构建完整提示
134
- _buildFullPrompt(messages: any[]): string {
135
- if (!messages || messages.length === 0) {
136
- return '';
137
- }
138
-
139
- let systemPrompt = '';
140
- const history: string[] = [];
141
- let lastUserMessage = '';
142
-
143
- for (const msg of messages) {
144
- if (msg.role === 'system' && !systemPrompt) {
145
- systemPrompt = msg.content;
146
- } else if (msg.role === 'user') {
147
- history.push(`user: ${msg.content}`);
148
- lastUserMessage = msg.content;
149
- } else if (msg.role === 'assistant') {
150
- history.push(`assistant: ${msg.content}`);
151
- }
152
- }
153
-
154
- const parts: string[] = [];
155
- if (systemPrompt) {
156
- parts.push(`[System Prompt]\n${systemPrompt}`);
157
- }
158
- if (history.length > 1) {
159
- parts.push(`[Chat History]\n${history.slice(0, -1).join('\n')}`);
160
- }
161
- parts.push(`[Question]\n${lastUserMessage}`);
162
-
163
- return parts.join('\n\n');
164
- }
165
-
166
- async* pipe(body: any) {
167
- const thinkingState = { thinking: -1 };
168
-
169
- // Build full prompt
170
- const fullPrompt = this._buildFullPrompt(body.messages);
171
-
172
- // Check if we need to force new conversation
173
- let forceNew = false;
174
- const messages = body.messages;
175
- if (messages.length === 1) {
176
- forceNew = true;
177
- } else if (messages.length >= 2) {
178
- const lastTwo = messages.slice(-2);
179
- if (lastTwo[0].role === 'user' && lastTwo[1].role === 'user') {
180
- forceNew = true;
181
- }
182
- }
183
-
184
- // Get or create device ID and conversation ID with User-Agent
185
- const { deviceId, conversationId: storedConversationId, userAgent } = this.chatManage.getOrCreateIds(forceNew);
186
- let conversationId = storedConversationId;
187
-
188
- // Create new conversation if needed
189
- if (!conversationId) {
190
- conversationId = await this._create_conversation(deviceId);
191
- if (!conversationId) {
192
- yield { error: 'Failed to create conversation' };
193
- return;
194
- }
195
- this.chatManage.updateConversationId(conversationId);
196
- }
197
-
198
- // Model name handling
199
- let modelName;
200
- const isSearchModel = body.model.endsWith('-Search');
201
- if (isSearchModel) {
202
- modelName = this.searchModels[body.model] || body.model.replace('-Search', '').toLowerCase();
203
- } else {
204
- const isDeepSeekModel = ['DeepSeek-R1', 'DeepSeek-V3'].includes(body.model);
205
- modelName = isDeepSeekModel ? 'deepseek' : body.model.toLowerCase();
206
- }
207
-
208
- // 确定 userAction 参数
209
- let userAction = '';
210
- if (body.model.includes('DeepSeek-R1')) {
211
- userAction = 'deep';
212
- }
213
- if (isSearchModel) {
214
- userAction = userAction ? `${userAction},online` : 'online';
215
- }
216
-
217
- const payload = {
218
- stream: true,
219
- botCode: 'AI_SEARCH',
220
- userAction,
221
- model: modelName,
222
- conversationId: conversationId,
223
- question: fullPrompt,
224
- };
225
-
226
- const timestamp = Math.floor(Date.now() / 1000).toString();
227
- const nonce = this.nanoid(21);
228
- const sign = await this.generateSign(timestamp, payload, nonce);
229
-
230
- const headers = {
231
- 'Origin': 'https://ai.dangbei.com',
232
- 'Referer': 'https://ai.dangbei.com/',
233
- 'User-Agent': userAgent,
234
- 'deviceId': deviceId,
235
- 'nonce': nonce,
236
- 'sign': sign,
237
- 'timestamp': timestamp,
238
- 'Content-Type': 'application/json',
239
- };
240
-
241
- try {
242
- const response = await fetch(`${API_DOMAIN}/ai-search/chatApi/v1/chat`, {
243
- method: 'POST',
244
- headers,
245
- body: JSON.stringify(payload),
246
- });
247
-
248
- if (!response.ok) {
249
- const error = await response.text();
250
- console.error('HTTP Error:', response.status, error);
251
- yield { error: `HTTP ${response.status}: ${error}` };
252
- return;
253
- }
254
-
255
- const reader = response.body!.getReader();
256
- const decoder = new TextDecoder();
257
- let buffer = '';
258
- let cardMessages: string[] = [];
259
-
260
- while (true) {
261
- const { done, value } = await reader.read();
262
- if (done) break;
263
-
264
- buffer += decoder.decode(value, { stream: true });
265
- const lines = buffer.split('\n');
266
- buffer = lines.pop() || '';
267
-
268
- for (const line of lines) {
269
- if (!line.startsWith(this.dataPrefix)) continue;
270
-
271
- try {
272
- const data = JSON.parse(line.slice(this.dataPrefix.length));
273
- if (data.type === 'answer') {
274
- const content = data.content;
275
- const contentType = data.content_type;
276
-
277
- if (thinkingState.thinking === -1 && contentType === 'thinking') {
278
- thinkingState.thinking = 0;
279
- yield { choices: [{ delta: { content: '<think>\n\n' }, finish_reason: null }] };
280
- } else if (thinkingState.thinking === 0 && contentType === 'text') {
281
- thinkingState.thinking = 1;
282
- yield { choices: [{ delta: { content: '\n' }, finish_reason: null }] };
283
- yield { choices: [{ delta: { content: '</think>' }, finish_reason: null }] };
284
- yield { choices: [{ delta: { content: '\n\n' }, finish_reason: null }] };
285
- }
286
-
287
- if (contentType === 'card') {
288
- try {
289
- const cardContent = JSON.parse(content);
290
- const cardItems = cardContent.cardInfo.cardItems;
291
- let markdownOutput = '\n\n---\n\n';
292
-
293
- const searchKeywords = cardItems.find((item: any) => item.type === '2001');
294
- if (searchKeywords) {
295
- const keywords = JSON.parse(searchKeywords.content);
296
- markdownOutput += `搜索关键字:${keywords.join('; ')}\n\n`;
297
- }
298
-
299
- const searchResults = cardItems.find((item: any) => item.type === '2002');
300
- if (searchResults) {
301
- const results = JSON.parse(searchResults.content);
302
- markdownOutput += `共找到 ${results.length} 个搜索结果:\n\n`;
303
-
304
- results.forEach((result: any) => {
305
- markdownOutput += `[${result.idIndex}] [${result.name}](${result.url}) 来源:${result.siteName}\n`;
306
- });
307
- }
308
-
309
- cardMessages.push(markdownOutput);
310
- } catch (e) {
311
- console.error('Error processing card:', e);
312
- }
313
- }
314
-
315
- if (content && (contentType === 'text' || contentType === 'thinking')) {
316
- yield { choices: [{ delta: { content }, finish_reason: null }] };
317
- }
318
- }
319
- } catch (e) {
320
- console.error('Parse error:', e, 'Line:', line);
321
- yield { error: `JSONDecodeError: ${(e as Error).message}` };
322
- return;
323
- }
324
- }
325
- }
326
-
327
- if (cardMessages.length > 0) {
328
- yield { choices: [{ delta: { content: cardMessages.join('') }, finish_reason: null }] };
329
- }
330
-
331
- yield {
332
- choices: [{
333
- delta: {
334
- meta: {
335
- device_id: deviceId,
336
- conversation_id: conversationId
337
- }
338
- },
339
- finish_reason: null
340
- }]
341
- };
342
-
343
- } catch (e) {
344
- console.error('Error in pipe:', e);
345
- yield { error: `${(e as Error).name}: ${(e as Error).message}` };
346
- }
347
- }
348
-
349
- nanoid(size = 21) {
350
- const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
351
- const bytes = new Uint8Array(size);
352
- crypto.getRandomValues(bytes);
353
- return Array.from(bytes).reverse().map(b => urlAlphabet[b & 63]).join('');
354
- }
355
-
356
- async generateSign(timestamp: string, payload: any, nonce: string) {
357
- const payloadStr = JSON.stringify(payload);
358
- const signStr = `${timestamp}${payloadStr}${nonce}`;
359
- console.log('Sign string:', signStr);
360
-
361
- // 使用 Deno 标准库的 MD5
362
- const sign = new Md5()
363
- .update(signStr)
364
- .toString()
365
- .toUpperCase();
366
-
367
- console.log('Generated sign:', sign);
368
- return sign;
369
- }
370
- }
371
-
372
- const pipe = new Pipe();
373
-
374
- // 验证 API 密钥
375
- function verifyApiKey(request: Request) {
376
- const authorization = request.headers.get('Authorization');
377
- // 检查环境变量是否配置
378
- if (!VALID_API_KEY) {
379
- return new Response(JSON.stringify({ error: 'API key not configured' }), {
380
- status: 500,
381
- headers: { 'Content-Type': 'application/json' },
382
- });
383
- }
384
-
385
- if (!authorization) {
386
- return new Response(JSON.stringify({ error: 'Missing API key' }), {
387
- status: 401,
388
- headers: {
389
- 'Content-Type': 'application/json',
390
- 'Access-Control-Allow-Origin': '*',
391
- },
392
- });
393
- }
394
-
395
- const apiKey = authorization.replace('Bearer ', '').trim();
396
- if (apiKey !== VALID_API_KEY) {
397
- return new Response(JSON.stringify({ error: 'Invalid API key' }), {
398
- status: 401,
399
- headers: {
400
- 'Content-Type': 'application/json',
401
- 'Access-Control-Allow-Origin': '*',
402
- },
403
- });
404
- }
405
-
406
- return null;
407
- }
408
-
409
- async function handleRequest(request: Request) {
410
- const url = new URL(request.url);
411
-
412
- // 添加根路径处理
413
- if (request.method === 'GET' && url.pathname === '/') {
414
- return new Response("it's work!", {
415
- headers: {
416
- 'Content-Type': 'text/plain',
417
- 'Access-Control-Allow-Origin': '*',
418
- },
419
- });
420
- }
421
-
422
- if (request.method === 'OPTIONS') {
423
- return new Response(null, {
424
- headers: {
425
- 'Access-Control-Allow-Origin': '*',
426
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
427
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
428
- },
429
- });
430
- }
431
-
432
- // 验证 API 密钥(除了 OPTIONS 请求)
433
- const authError = verifyApiKey(request);
434
- if (authError) return authError;
435
-
436
- if (request.method === 'GET' && url.pathname === '/v1/models') {
437
- const currentTime = Math.floor(Date.now() / 1000);
438
- return new Response(JSON.stringify({
439
- object: 'list',
440
- data: [
441
- // Original models
442
- {
443
- id: 'DeepSeek-R1',
444
- object: 'model',
445
- created: currentTime,
446
- owned_by: 'library'
447
- },
448
- {
449
- id: 'DeepSeek-V3',
450
- object: 'model',
451
- created: currentTime,
452
- owned_by: 'library'
453
- },
454
- {
455
- id: 'Doubao',
456
- object: 'model',
457
- created: currentTime,
458
- owned_by: 'library'
459
- },
460
- {
461
- id: 'Qwen',
462
- object: 'model',
463
- created: currentTime,
464
- owned_by: 'library'
465
- },
466
- {
467
- id: 'Glm3',
468
- object: 'model',
469
- created: currentTime,
470
- owned_by: 'library'
471
- },
472
- {
473
- id: 'Moonshot_v1',
474
- object: 'model',
475
- created: currentTime,
476
- owned_by: 'library'
477
- },
478
- // Search-enabled models
479
- {
480
- id: 'DeepSeek-R1-Search',
481
- object: 'model',
482
- created: currentTime,
483
- owned_by: 'library',
484
- features: ['online_search']
485
- },
486
- {
487
- id: 'DeepSeek-V3-Search',
488
- object: 'model',
489
- created: currentTime,
490
- owned_by: 'library',
491
- features: ['online_search']
492
- },
493
- {
494
- id: 'Doubao-Search',
495
- object: 'model',
496
- created: currentTime,
497
- owned_by: 'library',
498
- features: ['online_search']
499
- },
500
- {
501
- id: 'Qwen-Search',
502
- object: 'model',
503
- created: currentTime,
504
- owned_by: 'library',
505
- features: ['online_search']
506
- },
507
- {
508
- id: 'Glm3-Search',
509
- object: 'model',
510
- created: currentTime,
511
- owned_by: 'library',
512
- features: ['online_search']
513
- },
514
- {
515
- id: 'Moonshot_v1-Search',
516
- object: 'model',
517
- created: currentTime,
518
- owned_by: 'library',
519
- features: ['online_search']
520
- }
521
- ]
522
- }), {
523
- headers: {
524
- 'Content-Type': 'application/json',
525
- 'Access-Control-Allow-Origin': '*',
526
- },
527
- });
528
- }
529
-
530
- if (request.method === 'POST' && url.pathname === '/v1/chat/completions') {
531
- const body = await request.json();
532
- const isStream = body.stream || false;
533
-
534
- if (isStream) {
535
- const stream = new ReadableStream({
536
- async start(controller) {
537
- try {
538
- for await (const chunk of pipe.pipe(body)) {
539
- controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
540
- }
541
- controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
542
- controller.close();
543
- } catch (e) {
544
- console.error('Error in stream:', e);
545
- controller.error(e);
546
- }
547
- },
548
- });
549
-
550
- return new Response(stream, {
551
- headers: {
552
- 'Content-Type': 'text/event-stream',
553
- 'Cache-Control': 'no-cache',
554
- 'Connection': 'keep-alive',
555
- 'Access-Control-Allow-Origin': '*',
556
- },
557
- });
558
- }
559
-
560
- if (!isStream) {
561
- let content = '';
562
- let meta = null;
563
- let thinking_content: string[] = [];
564
- let is_thinking = false;
565
-
566
- try {
567
- for await (const chunk of pipe.pipe(body)) {
568
- if (chunk.choices?.[0]?.delta?.content) {
569
- const content_chunk = chunk.choices[0].delta.content;
570
- if (content_chunk === '<think>\n\n') {
571
- is_thinking = true;
572
- } else if (content_chunk === '\n</think>\n\n') {
573
- is_thinking = false;
574
- } else if (is_thinking) {
575
- thinking_content.push(content_chunk);
576
- } else {
577
- content += content_chunk;
578
- }
579
- }
580
- if (chunk.choices?.[0]?.delta?.meta) {
581
- meta = chunk.choices[0].delta.meta;
582
- }
583
- }
584
-
585
- // 处理思考内容
586
- const reasoningContent = thinking_content.join('');
587
-
588
- return new Response(JSON.stringify({
589
- id: crypto.randomUUID(),
590
- object: 'chat.completion',
591
- created: Math.floor(Date.now() / 1000),
592
- model: body.model,
593
- choices: [{
594
- message: {
595
- role: 'assistant',
596
- reasoning_content: reasoningContent ? `<think>\n${reasoningContent}\n</think>` : '',
597
- content: content.trim(),
598
- meta: meta
599
- },
600
- finish_reason: 'stop'
601
- }]
602
- } as NonStreamResponse), {
603
- headers: {
604
- 'Content-Type': 'application/json',
605
- 'Access-Control-Allow-Origin': '*',
606
- },
607
- });
608
- } catch (e) {
609
- console.error('Error processing chat request:', e);
610
- return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
611
- status: 500,
612
- headers: {
613
- 'Content-Type': 'application/json',
614
- 'Access-Control-Allow-Origin': '*',
615
- },
616
- });
617
- }
618
- }
619
- }
620
-
621
- return new Response('Not Found', { status: 404 });
622
- }
623
-
624
- serve(handleRequest, { port: 7860 });
625
-
626
- interface Message {
627
- role: string;
628
- content: string;
629
- }
630
-
631
- interface ChatRequest {
632
- model: string;
633
- messages: Message[];
634
- stream: boolean;
635
- temperature?: number;
636
- top_p?: number;
637
- n?: number;
638
- max_tokens?: number;
639
- presence_penalty?: number;
640
- frequency_penalty?: number;
641
- user?: string;
642
- }
643
-
644
- interface DeltaContent {
645
- content?: string;
646
- meta?: {
647
- device_id: string;
648
- conversation_id: string;
649
- };
650
- }
651
-
652
- interface Choice {
653
- delta: DeltaContent;
654
- finish_reason: string | null;
655
- }
656
-
657
- interface StreamResponse {
658
- choices?: Choice[];
659
- error?: string;
660
- }
661
-
662
- interface NonStreamResponse {
663
- id: string;
664
- object: string;
665
- created: number;
666
- model: string;
667
- choices: Array<{
668
- message: {
669
- role: string;
670
- reasoning_content: string;
671
- content: string;
672
- meta: {
673
- device_id: string;
674
- conversation_id: string;
675
- };
676
- };
677
- finish_reason: string;
678
- }>;
679
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt DELETED
@@ -1,8 +0,0 @@
1
- fastapi
2
- pydantic
3
- httpx[http2]
4
- uvicorn
5
- uuid
6
- python-dotenv
7
- fake-useragent
8
- httpx[socks]
 
 
 
 
 
 
 
 
 
version.py DELETED
@@ -1 +0,0 @@
1
- VERSION = '2025.05.10.0001'