XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Refactor DeepSeekAPI for improved authentication handling and add headers management; update requirements for wasmtime and numpy

2e85ad5b
hlohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

4 个文件 +50 -128
Modified g4f/Provider/needs_auth/DeepSeekAPI.py +16 -113
@@ -11,7 +11,7 @@ from pathlib import Path
11 11
12 12 from g4f.typing import AsyncResult, Messages, Cookies
13 13 from g4f.requests import StreamSession, raise_for_status, sse_stream, FormData
14 from g4f.cookies import get_cookies, get_cookies_dir
14 from g4f.cookies import get_cookies, get_headers, get_cookies_dir
15 15 from g4f.providers.response import (
16 16 JsonConversation, JsonRequest, JsonResponse,
17 17 Reasoning, FinishReason
@@ -163,76 +163,6 @@ def generate_client_stream_id() -> str:
163 163 hex_part = uuid.uuid4().hex[:16]
164 164 return f"{date_str}-{hex_part}"
165 165
166
167 def get_har_files():
168 """Get list of DeepSeek HAR files from har_and_cookies directory."""
169 if not os.access(get_cookies_dir(), os.R_OK):
170 return []
171
172 har_files = []
173 for root, _, files in os.walk(get_cookies_dir()):
174 for file in files:
175 # Look for DeepSeek HAR files
176 if file.endswith(".har") and "deepseek" in file.lower():
177 har_files.append(os.path.join(root, file))
178
179 # Sort by modification time, newest first
180 har_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
181 return har_files
182
183 def read_deepseek_har():
184 """
185 Read DeepSeek HAR file to extract cookies and auth token.
186
187 Returns:
188 dict with 'cookies' and 'authorization' keys or None if not found
189 """
190 import g4f.cookies
191
192 har_files = get_har_files()
193
194 if not har_files:
195 debug.log("DeepSeekAuth: No DeepSeek HAR files found in har_and_cookies/")
196 return None
197
198 # Read HAR files to get cookies and authorization header
199 for har_path in har_files:
200 debug.log(f"DeepSeekAuth: Reading HAR file: {har_path}")
201
202 # Get cookies using g4f's HAR parser
203 cookies_by_domain = g4f.cookies._parse_har_file(har_path)
204
205 # Look for DeepSeek cookies
206 deepseek_cookies = None
207 for domain, cookies in cookies_by_domain.items():
208 if 'deepseek.com' in domain:
209 deepseek_cookies = cookies
210 debug.log(f"DeepSeekAuth: Found {len(cookies)} cookies for {domain}")
211 break
212
213 if not deepseek_cookies:
214 continue
215
216 # Now look for authorization header in HAR
217 with open(har_path, 'r', encoding='utf-8') as f:
218 har_data = json.load(f)
219
220 for entry in har_data.get('log', {}).get('entries', []):
221 url = entry.get('request', {}).get('url', '')
222 if 'deepseek.com' in url.lower():
223 for header in entry.get('request', {}).get('headers', []):
224 if header.get('name', '').lower() == 'authorization':
225 auth_header = header.get('value')
226 debug.log(f"DeepSeekAuth: Found authorization token in HAR")
227 return {
228 "cookies": deepseek_cookies,
229 "authorization": auth_header
230 }
231
232 debug.log("DeepSeekAuth: No valid DeepSeek auth found in any HAR file")
233 return None
234
235
236 166 class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
237 167 """
238 168 DeepSeek provider using browser emulation with HAR file support.
@@ -245,7 +175,7 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
245 175 label = "DeepSeek (HAR Auth)"
246 176 url = DEEPSEEK_URL
247 177 cookie_domain = DEEPSEEK_DOMAIN
248 working = True
178 working = has_wasmtime_and_numpy
249 179 active_by_default = True
250 180 needs_auth = True
251 181 supports_file_upload = True
@@ -392,6 +322,7 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
392 322 model: str,
393 323 messages: Messages,
394 324 cookies: Cookies = None,
325 headers: dict = None,
395 326 proxy: str = None,
396 327 conversation: JsonConversation = None,
397 328 web_search: bool = False,
@@ -422,23 +353,17 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
422 353 model = cls.default_model
423 354
424 355 # Try to get auth from HAR file first
425 auth_data = None
426 356 if cookies is None:
427 auth_data = read_deepseek_har()
428 if auth_data:
429 cookies = auth_data.get("cookies")
430 debug.log(f"DeepSeekAuth: Using {len(cookies)} cookies from HAR file")
357 cookies = get_cookies(cls.cookie_domain, False)
358 headers = get_headers(cls.cookie_domain)
359 if cookies:
360 debug.log(f"DeepSeekAuth: Using {len(cookies)} cookies and {len(headers)} headers from cookie jar")
431 361 else:
432 # Fall back to cookie jar
433 cookies = get_cookies(cls.cookie_domain, False)
434 if cookies:
435 debug.log(f"DeepSeekAuth: Using {len(cookies)} cookies from cookie jar")
436 else:
437 raise MissingAuthError(
438 "DeepSeekAuth: No authentication found. "
439 "Please add a DeepSeek HAR file to har_and_cookies/ directory "
440 "with an authorization token."
441 )
362 raise MissingAuthError(
363 "DeepSeekAuth: No authentication found. "
364 "Please add a DeepSeek HAR file to har_and_cookies/ directory "
365 "with an authorization token."
366 )
442 367
443 368 # Initialize conversation if needed
444 369 if conversation is None:
@@ -448,8 +373,8 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
448 373
449 374 # Get auth token from HAR data or conversation
450 375 authorization = None
451 if auth_data:
452 authorization = auth_data.get("authorization")
376 if headers:
377 authorization = headers.get("authorization")
453 378 elif hasattr(conversation, 'authorization'):
454 379 authorization = conversation.authorization
455 380
@@ -587,22 +512,7 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
587 512 # Check if response is actually SSE or regular JSON
588 513 content_type = response.headers.get('content-type', '')
589 514 if 'text/event-stream' not in content_type.lower():
590 # Not a streaming response - try regular JSON
591 # debug.log(f"DeepSeekAuth: Response is NOT SSE (content-type: {content_type})")
592 data = await response.json()
593 # debug.log(f"DeepSeekAuth: Full response: {data}")
594
595 # Check for content in response
596 if 'content' in data:
597 content = data.get('content', '')
598 yield content
599 if 'choices' in data and len(data['choices']) > 0:
600 choice = data['choices'][0]
601 if 'message' in choice and 'content' in choice['message']:
602 yield choice['message']['content']
603 if 'finish_reason' in data:
604 yield FinishReason(data['finish_reason'])
605 return
515 raise RuntimeError(f"Expected SSE response but got content-type: {content_type}")
606 516
607 517 is_thinking = False
608 518 async for stream_data in sse_stream(response):
@@ -666,14 +576,7 @@ class DeepSeekAPI(AsyncGeneratorProvider, ProviderModelMixin):
666 576 elif 'v' in stream_data and isinstance(stream_data['v'], str):
667 577 yield Reasoning(stream_data['v']) if is_thinking else stream_data['v']
668 578 # debug.log(f"DeepSeekAuth: Shorthand content: '{stream_data['v']}'")
669
670 # Handle finish reason
671 elif isinstance(stream_data, FinishReason):
672 if hasattr(stream_data, 'response_message_id'):
673 conversation.parent_message_id = stream_data.response_message_id
674 yield conversation
675 yield stream_data
676 break
579
677 580
678 581 # Ensure we yield the conversation object at the end
679 582 yield conversation
Modified g4f/cookies.py +28 -13
@@ -43,6 +43,9 @@ from .errors import MissingRequirementsError
43 43 from .config import AppConfig, COOKIES_DIR, CUSTOM_COOKIES_DIR
44 44 from . import debug
45 45
46 class HeadersConfig:
47 headers: Dict[str, Dict[str, str]] = {}
48
46 49 class CookiesConfig:
47 50 cookies: Dict[str, Cookies] = {}
48 51 cookies_dir: str = CUSTOM_COOKIES_DIR if os.path.exists(CUSTOM_COOKIES_DIR) else str(COOKIES_DIR)
@@ -58,7 +61,7 @@ class BrowserConfig:
58 61
59 62 browser_executable_path: str = None
60 63
61 DOMAINS = (
64 COOKIE_DOMAINS = (
62 65 ".bing.com",
63 66 ".meta.ai",
64 67 ".google.com",
@@ -70,13 +73,18 @@ DOMAINS = (
70 73 ".cerebras.ai",
71 74 "github.com",
72 75 "yupp.ai",
73 "deepseek.com",
76 "chat.deepseek.com",
74 77 )
75 78
76 79 if has_browser_cookie3 and os.environ.get("DBUS_SESSION_BUS_ADDRESS", "/dev/null") == "/dev/null":
77 80 _LinuxPasswordManager.get_password = lambda a, b: b"secret"
78 81
79 82
83 def get_headers(domain_name: str) -> Dict[str, str]:
84 """Get cached headers for a domain."""
85 return HeadersConfig.headers.get(domain_name, {})
86
87
80 88 def get_cookies(domain_name: str, raise_requirements_error: bool = True,
81 89 single_browser: bool = False, cache_result: bool = True) -> Dict[str, str]:
82 90 """Load cookies for a given domain from all supported browsers."""
@@ -135,6 +143,19 @@ def get_cookies_dir() -> str:
135 143 return CookiesConfig.cookies_dir
136 144
137 145
146 def _get_domain(entry: dict) -> Optional[str]:
147 headers = entry["request"].get("headers", [])
148 host_values = [h["value"] for h in headers if h["name"].lower() in ("host", ":authority")]
149 if not host_values:
150 return None
151 host = host_values.pop()
152 return next((d for d in COOKIE_DOMAINS if d in host), None)
153
154
155 def _get_headers(entry) -> dict:
156 return {h['name'].lower(): h['value'] for h in entry['request']['headers'] if h['name'].lower() not in ['content-length', 'cookie'] and not h['name'].startswith(':')}
157
158
138 159 def _parse_har_file(path: str) -> Dict[str, Dict[str, str]]:
139 160 """Parse a HAR file and return cookies by domain."""
140 161 cookies_by_domain = {}
@@ -143,17 +164,10 @@ def _parse_har_file(path: str) -> Dict[str, Dict[str, str]]:
143 164 har_file = json.load(file)
144 165 debug.log(f"Read .har file: {path}")
145 166
146 def get_domain(entry: dict) -> Optional[str]:
147 headers = entry["request"].get("headers", [])
148 host_values = [h["value"] for h in headers if h["name"].lower() in ("host", ":authority")]
149 if not host_values:
150 return None
151 host = host_values.pop()
152 return next((d for d in DOMAINS if d in host), None)
153
154 167 for entry in har_file.get("log", {}).get("entries", []):
155 domain = get_domain(entry)
168 domain = _get_domain(entry)
156 169 if domain:
170 HeadersConfig.headers[domain] = {**HeadersConfig.headers.get(domain, {}), **_get_headers(entry)}
157 171 v_cookies = {c["name"]: c["value"] for c in entry["request"].get("cookies", [])}
158 172 if v_cookies:
159 173 cookies_by_domain[domain] = v_cookies
@@ -191,8 +205,9 @@ def read_cookie_files(dir_path: Optional[str] = None, domains_filter: Optional[L
191 205 # Optionally load environment variables
192 206 try:
193 207 from dotenv import load_dotenv
194 load_dotenv(os.path.join(dir_path, ".env"), override=True)
195 debug.log(f"Read cookies: Loaded env vars from {dir_path}/.env")
208 env_path = os.path.join(dir_path, ".env")
209 load_dotenv(env_path, override=True)
210 debug.log(f"Read cookies: Loaded env vars from {env_path}")
196 211 except ImportError:
197 212 debug.error("Warning: 'python-dotenv' is not installed. Env vars not loaded.")
198 213
Modified requirements.txt +3 -1
@@ -18,4 +18,6 @@ python-multipart
18 18 a2wsgi
19 19 python-dotenv
20 20 ddgs
21 cloudscraper
21 cloudscraper
22 wasmtime
23 numpy
Modified setup.py +3 -1
@@ -39,7 +39,9 @@ EXTRA_REQUIRE = {
39 39 "markitdown[all]",
40 40 "python-dotenv",
41 41 "aiofile",
42 "cloudscraper"
42 "cloudscraper",
43 "wasmtime",
44 "numpy"
43 45 ],
44 46 'slim': [
45 47 "curl_cffi>=0.6.2",