Spaces:
Paused
Paused
Update proxy_handler.py
Browse files- proxy_handler.py +12 -22
proxy_handler.py
CHANGED
|
@@ -22,7 +22,6 @@ class ProxyHandler:
|
|
| 22 |
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
| 23 |
http2=True,
|
| 24 |
)
|
| 25 |
-
# The primary secret key from the reference code.
|
| 26 |
self.primary_secret = "junjie".encode('utf-8')
|
| 27 |
|
| 28 |
async def aclose(self):
|
|
@@ -33,24 +32,21 @@ class ProxyHandler:
|
|
| 33 |
return int(time.time() * 1000)
|
| 34 |
|
| 35 |
def _parse_jwt_token(self, token: str) -> Dict[str, str]:
|
| 36 |
-
"""A simple JWT payload decoder to get user ID ('sub' claim)."""
|
| 37 |
try:
|
| 38 |
parts = token.split('.')
|
| 39 |
if len(parts) != 3: return {"user_id": ""}
|
| 40 |
payload_b64 = parts[1]
|
| 41 |
-
payload_b64 += '=' * (-len(payload_b64) % 4)
|
| 42 |
payload_json = base64.urlsafe_b64decode(payload_b64).decode('utf-8')
|
| 43 |
payload = json.loads(payload_json)
|
| 44 |
return {"user_id": payload.get("sub", "")}
|
| 45 |
except Exception:
|
| 46 |
-
# It's okay if this fails; we'll proceed with an empty user_id.
|
| 47 |
return {"user_id": ""}
|
| 48 |
|
| 49 |
-
# MODIFICATION START: The function now accepts the timestamp to ensure consistency.
|
| 50 |
def _generate_signature(self, e_payload: str, t_payload: str, timestamp_ms: int) -> Dict[str, Any]:
|
| 51 |
"""
|
| 52 |
Generates the signature based on the logic from the reference JS code.
|
| 53 |
-
|
| 54 |
|
| 55 |
Args:
|
| 56 |
e_payload (str): The simplified payload string (e.g., "requestId,...,timestamp,...").
|
|
@@ -61,20 +57,23 @@ class ProxyHandler:
|
|
| 61 |
A dictionary with 'signature' and 'timestamp'.
|
| 62 |
"""
|
| 63 |
b64_encoded_t = base64.b64encode(t_payload.encode("utf-8")).decode("utf-8")
|
| 64 |
-
|
| 65 |
-
# Use the passed-in timestamp instead of generating a new one.
|
| 66 |
message_string = f"{e_payload}|{b64_encoded_t}|{timestamp_ms}"
|
| 67 |
-
|
| 68 |
n = timestamp_ms // (5 * 60 * 1000)
|
| 69 |
|
|
|
|
|
|
|
|
|
|
| 70 |
msg1 = str(n).encode("utf-8")
|
| 71 |
-
|
| 72 |
|
|
|
|
|
|
|
| 73 |
msg2 = message_string.encode("utf-8")
|
| 74 |
-
final_signature = hmac.new(
|
|
|
|
|
|
|
| 75 |
|
| 76 |
return {"signature": final_signature, "timestamp": timestamp_ms}
|
| 77 |
-
# MODIFICATION END
|
| 78 |
|
| 79 |
def _clean_thinking_content(self, text: str) -> str:
|
| 80 |
if not text: return ""
|
|
@@ -88,7 +87,7 @@ class ProxyHandler:
|
|
| 88 |
def _clean_answer_content(self, text: str) -> str:
|
| 89 |
if not text: return ""
|
| 90 |
cleaned_text = re.sub(r'<details[^>]*>.*?</details>', '', text, flags=re.DOTALL)
|
| 91 |
-
cleaned_text = re.sub(r'<glm_block.*?</glm_block>|<summary>.*?</summary>', '',
|
| 92 |
cleaned_text = re.sub(r'\s*duration="\d+"[^>]*>', '', cleaned_text)
|
| 93 |
return cleaned_text
|
| 94 |
|
|
@@ -102,36 +101,28 @@ class ProxyHandler:
|
|
| 102 |
return out
|
| 103 |
|
| 104 |
async def _prep_upstream(self, req: ChatCompletionRequest) -> Tuple[Dict[str, Any], Dict[str, str], str, str]:
|
| 105 |
-
"""Prepares the request body, headers, cookie, and URL for the upstream API."""
|
| 106 |
ck = await cookie_manager.get_next_cookie()
|
| 107 |
if not ck: raise HTTPException(503, "No available cookies")
|
| 108 |
|
| 109 |
model = settings.UPSTREAM_MODEL if req.model == settings.MODEL_NAME else req.model
|
| 110 |
|
| 111 |
-
# MODIFICATION START: Generate timestamp ONCE and reuse it everywhere.
|
| 112 |
timestamp_ms = self._get_timestamp_millis()
|
| 113 |
payload_timestamp = str(timestamp_ms)
|
| 114 |
-
# MODIFICATION END
|
| 115 |
|
| 116 |
payload_user_id = str(uuid.uuid4())
|
| 117 |
payload_request_id = str(uuid.uuid4())
|
| 118 |
|
| 119 |
-
# e: The simplified payload for the signature, now using the single consistent timestamp.
|
| 120 |
e_payload = f"requestId,{payload_request_id},timestamp,{payload_timestamp},user_id,{payload_user_id}"
|
| 121 |
|
| 122 |
-
# t: The last message content
|
| 123 |
t_payload = ""
|
| 124 |
if req.messages:
|
| 125 |
last_message = req.messages[-1]
|
| 126 |
if isinstance(last_message.content, str):
|
| 127 |
t_payload = last_message.content
|
| 128 |
|
| 129 |
-
# MODIFICATION START: Pass the consistent timestamp into the signature generator.
|
| 130 |
signature_data = self._generate_signature(e_payload, t_payload, timestamp_ms)
|
| 131 |
-
# MODIFICATION END
|
| 132 |
|
| 133 |
signature = signature_data["signature"]
|
| 134 |
-
# This timestamp now correctly matches the one in the URL params.
|
| 135 |
signature_timestamp = signature_data["timestamp"]
|
| 136 |
|
| 137 |
url_params = {
|
|
@@ -288,7 +279,6 @@ class ProxyHandler:
|
|
| 288 |
logger.exception("Non-stream processing failed"); raise
|
| 289 |
|
| 290 |
async def handle_chat_completion(self, req: ChatCompletionRequest):
|
| 291 |
-
"""Determines whether to stream or not and handles the request."""
|
| 292 |
stream = bool(req.stream) if req.stream is not None else settings.DEFAULT_STREAM
|
| 293 |
if stream:
|
| 294 |
return StreamingResponse(self.stream_proxy_response(req), media_type="text/event-stream",
|
|
|
|
| 22 |
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
| 23 |
http2=True,
|
| 24 |
)
|
|
|
|
| 25 |
self.primary_secret = "junjie".encode('utf-8')
|
| 26 |
|
| 27 |
async def aclose(self):
|
|
|
|
| 32 |
return int(time.time() * 1000)
|
| 33 |
|
| 34 |
def _parse_jwt_token(self, token: str) -> Dict[str, str]:
|
|
|
|
| 35 |
try:
|
| 36 |
parts = token.split('.')
|
| 37 |
if len(parts) != 3: return {"user_id": ""}
|
| 38 |
payload_b64 = parts[1]
|
| 39 |
+
payload_b64 += '=' * (-len(payload_b64) % 4)
|
| 40 |
payload_json = base64.urlsafe_b64decode(payload_b64).decode('utf-8')
|
| 41 |
payload = json.loads(payload_json)
|
| 42 |
return {"user_id": payload.get("sub", "")}
|
| 43 |
except Exception:
|
|
|
|
| 44 |
return {"user_id": ""}
|
| 45 |
|
|
|
|
| 46 |
def _generate_signature(self, e_payload: str, t_payload: str, timestamp_ms: int) -> Dict[str, Any]:
|
| 47 |
"""
|
| 48 |
Generates the signature based on the logic from the reference JS code.
|
| 49 |
+
This version corrects the HMAC chaining issue by using .digest() for the intermediate key.
|
| 50 |
|
| 51 |
Args:
|
| 52 |
e_payload (str): The simplified payload string (e.g., "requestId,...,timestamp,...").
|
|
|
|
| 57 |
A dictionary with 'signature' and 'timestamp'.
|
| 58 |
"""
|
| 59 |
b64_encoded_t = base64.b64encode(t_payload.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
|
| 60 |
message_string = f"{e_payload}|{b64_encoded_t}|{timestamp_ms}"
|
|
|
|
| 61 |
n = timestamp_ms // (5 * 60 * 1000)
|
| 62 |
|
| 63 |
+
# --- MODIFICATION START: Correct HMAC Chaining ---
|
| 64 |
+
|
| 65 |
+
# 1. First HMAC: Calculate intermediate key as RAW BYTES using .digest()
|
| 66 |
msg1 = str(n).encode("utf-8")
|
| 67 |
+
intermediate_key_bytes = hmac.new(self.primary_secret, msg1, hashlib.sha256).digest()
|
| 68 |
|
| 69 |
+
# 2. Second HMAC: Use the raw bytes of the intermediate key directly.
|
| 70 |
+
# The final result is converted to a hex string for the header.
|
| 71 |
msg2 = message_string.encode("utf-8")
|
| 72 |
+
final_signature = hmac.new(intermediate_key_bytes, msg2, hashlib.sha256).hexdigest()
|
| 73 |
+
|
| 74 |
+
# --- MODIFICATION END ---
|
| 75 |
|
| 76 |
return {"signature": final_signature, "timestamp": timestamp_ms}
|
|
|
|
| 77 |
|
| 78 |
def _clean_thinking_content(self, text: str) -> str:
|
| 79 |
if not text: return ""
|
|
|
|
| 87 |
def _clean_answer_content(self, text: str) -> str:
|
| 88 |
if not text: return ""
|
| 89 |
cleaned_text = re.sub(r'<details[^>]*>.*?</details>', '', text, flags=re.DOTALL)
|
| 90 |
+
cleaned_text = re.sub(r'<glm_block.*?</glm_block>|<summary>.*?</summary>', '', text, flags=re.DOTALL)
|
| 91 |
cleaned_text = re.sub(r'\s*duration="\d+"[^>]*>', '', cleaned_text)
|
| 92 |
return cleaned_text
|
| 93 |
|
|
|
|
| 101 |
return out
|
| 102 |
|
| 103 |
async def _prep_upstream(self, req: ChatCompletionRequest) -> Tuple[Dict[str, Any], Dict[str, str], str, str]:
|
|
|
|
| 104 |
ck = await cookie_manager.get_next_cookie()
|
| 105 |
if not ck: raise HTTPException(503, "No available cookies")
|
| 106 |
|
| 107 |
model = settings.UPSTREAM_MODEL if req.model == settings.MODEL_NAME else req.model
|
| 108 |
|
|
|
|
| 109 |
timestamp_ms = self._get_timestamp_millis()
|
| 110 |
payload_timestamp = str(timestamp_ms)
|
|
|
|
| 111 |
|
| 112 |
payload_user_id = str(uuid.uuid4())
|
| 113 |
payload_request_id = str(uuid.uuid4())
|
| 114 |
|
|
|
|
| 115 |
e_payload = f"requestId,{payload_request_id},timestamp,{payload_timestamp},user_id,{payload_user_id}"
|
| 116 |
|
|
|
|
| 117 |
t_payload = ""
|
| 118 |
if req.messages:
|
| 119 |
last_message = req.messages[-1]
|
| 120 |
if isinstance(last_message.content, str):
|
| 121 |
t_payload = last_message.content
|
| 122 |
|
|
|
|
| 123 |
signature_data = self._generate_signature(e_payload, t_payload, timestamp_ms)
|
|
|
|
| 124 |
|
| 125 |
signature = signature_data["signature"]
|
|
|
|
| 126 |
signature_timestamp = signature_data["timestamp"]
|
| 127 |
|
| 128 |
url_params = {
|
|
|
|
| 279 |
logger.exception("Non-stream processing failed"); raise
|
| 280 |
|
| 281 |
async def handle_chat_completion(self, req: ChatCompletionRequest):
|
|
|
|
| 282 |
stream = bool(req.stream) if req.stream is not None else settings.DEFAULT_STREAM
|
| 283 |
if stream:
|
| 284 |
return StreamingResponse(self.stream_proxy_response(req), media_type="text/event-stream",
|