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

XFEstudio/gpt4free

Add full nodriver support to OpenaiChat Move small docker images before old images

2a7770ea
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

5 个文件 +126 -107
Modified .github/workflows/publish-workflow.yaml +10 -8
@@ -37,26 +37,28 @@ jobs:
37 37 username: ${{ github.repository_owner }}
38 38 password: ${{ secrets.GHCR_PAT }}
39 39
40 - name: Build and push image
40 - name: Build and push small images
41 41 uses: docker/build-push-action@v5
42 42 with:
43 43 context: .
44 file: docker/Dockerfile
44 file: docker/Dockerfile-slim
45 45 platforms: linux/amd64,linux/arm64
46 46 push: true
47 tags: ${{ steps.metadata.outputs.tags }}
47 tags: |
48 hlohaus789/g4f:latest-slim
49 hlohaus789/g4f:${{ github.ref_name }}-slim
48 50 labels: ${{ steps.metadata.outputs.labels }}
49 51 build-args: |
50 52 G4F_VERSION=${{ github.ref_name }}
51 - name: Build and push slim image
53
54 - name: Build and push big images
52 55 uses: docker/build-push-action@v5
53 56 with:
54 57 context: .
55 file: docker/Dockerfile-slim
58 file: docker/Dockerfile
59 platforms: linux/amd64,linux/arm64
56 60 push: true
57 tags: |
58 hlohaus789/g4f:slim-latest
59 hlohaus789/g4f:${{ github.ref_name }}-slim
61 tags: ${{ steps.metadata.outputs.tags }}
60 62 labels: ${{ steps.metadata.outputs.labels }}
61 63 build-args: |
62 64 G4F_VERSION=${{ github.ref_name }}
Modified g4f/Provider/needs_auth/OpenaiChat.py +73 -55
@@ -1,5 +1,6 @@
1 1 from __future__ import annotations
2 2
3 import re
3 4 import asyncio
4 5 import uuid
5 6 import json
@@ -10,6 +11,7 @@ from copy import copy
10 11
11 12 try:
12 13 import nodriver
14 from nodriver.cdp.network import get_response_body
13 15 has_nodriver = True
14 16 except ImportError:
15 17 has_nodriver = False
@@ -27,8 +29,10 @@ from ...image import ImageResponse, ImageRequest, to_image, to_bytes, is_accepte
27 29 from ...errors import MissingAuthError, ResponseError
28 30 from ...providers.conversation import BaseConversation
29 31 from ..helper import format_cookies
30 from ..openai.har_file import getArkoseAndAccessToken, NoValidHarFileError
32 from ..openai.har_file import get_request_config, NoValidHarFileError
33 from ..openai.har_file import RequestConfig, arkReq, arkose_url, start_url, conversation_url, backend_url, backend_anon_url
31 34 from ..openai.proofofwork import generate_proof_token
35 from ..openai.new import get_requirements_token
32 36 from ... import debug
33 37
34 38 DEFAULT_HEADERS = {
@@ -56,9 +60,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
56 60 supports_gpt_4 = True
57 61 supports_message_history = True
58 62 supports_system_message = True
59 default_model = None
63 default_model = "auto"
60 64 default_vision_model = "gpt-4o"
61 models = [ "auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"]
65 models = ["auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"]
62 66
63 67 _api_key: str = None
64 68 _headers: dict = None
@@ -352,21 +356,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
352 356 ) as session:
353 357 if cls._expires is not None and cls._expires < time.time():
354 358 cls._headers = cls._api_key = None
355 arkose_token = None
356 proofToken = None
357 turnstileToken = None
358 359 try:
359 arkose_token, api_key, cookies, headers, proofToken, turnstileToken = await getArkoseAndAccessToken(proxy)
360 cls._create_request_args(cookies, headers)
361 cls._set_api_key(api_key)
360 await get_request_config(proxy)
361 cls._create_request_args(RequestConfig.cookies, RequestConfig.headers)
362 cls._set_api_key(RequestConfig.access_token)
362 363 except NoValidHarFileError as e:
363 if cls._api_key is None and cls.needs_auth:
364 raise e
365 cls._create_request_args()
366
367 if cls.default_model is None:
368 cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers))
369
364 await cls.nodriver_auth(proxy)
370 365 try:
371 366 image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None
372 367 except Exception as e:
@@ -374,9 +369,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
374 369 if debug.logging:
375 370 print("OpenaiChat: Upload image failed")
376 371 print(f"{e.__class__.__name__}: {e}")
377
378 372 model = cls.get_model(model)
379 model = "text-davinci-002-render-sha" if model == "gpt-3.5-turbo" else model
380 373 if conversation is None:
381 374 conversation = Conversation(conversation_id, str(uuid.uuid4()) if parent_id is None else parent_id)
382 375 else:
@@ -389,7 +382,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
389 382 f"{cls.url}/backend-anon/sentinel/chat-requirements"
390 383 if cls._api_key is None else
391 384 f"{cls.url}/backend-api/sentinel/chat-requirements",
392 json={"p": generate_proof_token(True, user_agent=cls._headers["user-agent"], proofToken=proofToken)},
385 json={"p": get_requirements_token(RequestConfig.proof_token)},
393 386 headers=cls._headers
394 387 ) as response:
395 388 cls._update_request_args(session)
@@ -399,22 +392,22 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
399 392 need_arkose = chat_requirements.get("arkose", {}).get("required", False)
400 393 chat_token = chat_requirements.get("token")
401 394
402 if need_arkose and arkose_token is None:
403 arkose_token, api_key, cookies, headers, proofToken, turnstileToken = await getArkoseAndAccessToken(proxy)
404 cls._create_request_args(cookies, headers)
405 cls._set_api_key(api_key)
406 if arkose_token is None:
395 if need_arkose and RequestConfig.arkose_token is None:
396 await get_request_config(proxy)
397 cls._create_request_args(RequestConfig,cookies, RequestConfig.headers)
398 cls._set_api_key(RequestConfig.access_token)
399 if RequestConfig.arkose_token is None:
407 400 raise MissingAuthError("No arkose token found in .har file")
408 401
409 402 if "proofofwork" in chat_requirements:
410 403 proofofwork = generate_proof_token(
411 404 **chat_requirements["proofofwork"],
412 405 user_agent=cls._headers["user-agent"],
413 proofToken=proofToken
406 proof_token=RequestConfig.proof_token
414 407 )
415 408 if debug.logging:
416 409 print(
417 'Arkose:', False if not need_arkose else arkose_token[:12]+"...",
410 'Arkose:', False if not need_arkose else RequestConfig.arkose_token[:12]+"...",
418 411 'Proofofwork:', False if proofofwork is None else proofofwork[:12]+"...",
419 412 )
420 413 ws = None
@@ -423,7 +416,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
423 416 wss_url = (await response.json()).get("wss_url")
424 417 if wss_url:
425 418 ws = await session.ws_connect(wss_url)
426 websocket_request_id = str(uuid.uuid4())
427 419 data = {
428 420 "action": action,
429 421 "messages": None,
@@ -432,7 +424,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
432 424 "paragen_cot_summary_display_override": "allow",
433 425 "history_and_training_disabled": history_disabled and not auto_continue and not return_conversation,
434 426 "conversation_mode": {"kind":"primary_assistant"},
435 "websocket_request_id": websocket_request_id,
427 "websocket_request_id": str(uuid.uuid4()),
436 428 "supported_encodings": ["v1"],
437 429 "supports_buffering": True
438 430 }
@@ -446,12 +438,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
446 438 "Openai-Sentinel-Chat-Requirements-Token": chat_token,
447 439 **cls._headers
448 440 }
449 if arkose_token:
450 headers["Openai-Sentinel-Arkose-Token"] = arkose_token
441 if RequestConfig.arkose_token:
442 headers["Openai-Sentinel-Arkose-Token"] = RequestConfig.arkose_token
451 443 if proofofwork is not None:
452 444 headers["Openai-Sentinel-Proof-Token"] = proofofwork
453 if need_turnstile and turnstileToken is not None:
454 headers['openai-sentinel-turnstile-token'] = turnstileToken
445 if need_turnstile and RequestConfig.turnstile_token is not None:
446 headers['openai-sentinel-turnstile-token'] = RequestConfig.turnstile_token
455 447 async with session.post(
456 448 f"{cls.url}/backend-anon/conversation"
457 449 if cls._api_key is None else
@@ -535,7 +527,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
535 527 return
536 528 if isinstance(line, dict) and "v" in line:
537 529 v = line.get("v")
538 r = ""
539 530 if isinstance(v, str):
540 531 yield v
541 532 elif isinstance(v, list):
@@ -567,7 +558,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
567 558 raise RuntimeError(line.get("error"))
568 559
569 560 @classmethod
570 async def nodriver_access_token(cls, proxy: str = None):
561 async def nodriver_auth(cls, proxy: str = None):
571 562 if not has_nodriver:
572 563 return
573 564 if has_platformdirs:
@@ -580,27 +571,53 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
580 571 user_data_dir=user_data_dir,
581 572 browser_args=None if proxy is None else [f"--proxy-server={proxy}"],
582 573 )
583 page = await browser.get("https://chatgpt.com/")
584 await page.select("[id^=headlessui-menu-button-]", 240)
585 api_key = await page.evaluate(
586 "(async () => {"
587 "let session = await fetch('/api/auth/session');"
588 "let data = await session.json();"
589 "let accessToken = data['accessToken'];"
590 "let expires = new Date(); expires.setTime(expires.getTime() + 60 * 60 * 4 * 1000);"
591 "document.cookie = 'access_token=' + accessToken + ';expires=' + expires.toUTCString() + ';path=/';"
592 "return accessToken;"
593 "})();",
594 await_promise=True
595 )
596 cookies = {}
597 for c in await page.browser.cookies.get_all():
598 if c.domain.endswith("chatgpt.com"):
599 cookies[c.name] = c.value
600 user_agent = await page.evaluate("window.navigator.userAgent")
574 page = browser.main_tab
575 def on_request(event: nodriver.cdp.network.RequestWillBeSent):
576 if event.request.url == start_url or event.request.url.startswith(conversation_url):
577 RequestConfig.access_request_id = event.request_id
578 RequestConfig.headers = event.request.headers
579 elif event.request.url in (backend_url, backend_anon_url):
580 if "OpenAI-Sentinel-Proof-Token" in event.request.headers:
581 RequestConfig.proof_token = json.loads(base64.b64decode(
582 event.request.headers["OpenAI-Sentinel-Proof-Token"].split("gAAAAAB", 1)[-1].encode()
583 ).decode())
584 if "OpenAI-Sentinel-Turnstile-Token" in event.request.headers:
585 RequestConfig.turnstile_token = event.request.headers["OpenAI-Sentinel-Turnstile-Token"]
586 if "Authorization" in event.request.headers:
587 RequestConfig.access_token = event.request.headers["Authorization"].split()[-1]
588 elif event.request.url == arkose_url:
589 RequestConfig.arkose_request = arkReq(
590 arkURL=event.request.url,
591 arkBx=None,
592 arkHeader=event.request.headers,
593 arkBody=event.request.post_data,
594 userAgent=event.request.headers.get("user-agent")
595 )
596 await page.send(nodriver.cdp.network.enable())
597 page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
598 page = await browser.get(cls.url)
599 try:
600 if RequestConfig.access_request_id is not None:
601 body = await page.send(get_response_body(RequestConfig.access_request_id))
602 if isinstance(body, tuple) and body:
603 body = body[0]
604 if body:
605 match = re.search(r'"accessToken":"(.*?)"', body)
606 if match:
607 RequestConfig.access_token = match.group(1)
608 except KeyError:
609 pass
610 for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])):
611 RequestConfig.cookies[c.name] = c.value
612 RequestConfig.user_agent = await page.evaluate("window.navigator.userAgent")
613 await page.select("#prompt-textarea", 240)
614 while True:
615 if RequestConfig.proof_token:
616 break
617 await asyncio.sleep(1)
601 618 await page.close()
602 cls._create_request_args(cookies, user_agent=user_agent)
603 cls._set_api_key(api_key)
619 cls._create_request_args(RequestConfig.cookies, RequestConfig.headers, user_agent=RequestConfig.user_agent)
620 cls._set_api_key(RequestConfig.access_token)
604 621
605 622 @staticmethod
606 623 def get_default_headers() -> dict:
@@ -627,7 +644,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
627 644 def _set_api_key(cls, api_key: str):
628 645 cls._api_key = api_key
629 646 cls._expires = int(time.time()) + 60 * 60 * 4
630 cls._headers["authorization"] = f"Bearer {api_key}"
647 if api_key:
648 cls._headers["authorization"] = f"Bearer {api_key}"
631 649
632 650 @classmethod
633 651 def _update_cookie_header(cls):
Modified g4f/Provider/openai/har_file.py +36 -36
@@ -15,8 +15,27 @@ from ...requests import StreamSession
15 15 from ...cookies import get_cookies_dir
16 16 from ... import debug
17 17
18 arkose_url = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
19 backend_url = "https://chatgpt.com/backend-api/conversation"
20 backend_anon_url = "https://chatgpt.com/backend-anon/conversation"
21 start_url = "https://chatgpt.com/"
22 conversation_url = "https://chatgpt.com/c/"
23
18 24 class NoValidHarFileError(Exception):
19 ...
25 pass
26
27 class RequestConfig:
28 user_agent: str = None
29 cookies: dict = None
30 headers: dict = None
31 access_request_id: str = None
32 access_token: str = None
33 proof_token: list = None
34 turnstile_token: str = None
35 arkose_request: arkReq = None
36 arkose_token: str = None
37 headers: dict = {}
38 cookies: dict = {}
20 39
21 40 class arkReq:
22 41 def __init__(self, arkURL, arkBx, arkHeader, arkBody, arkCookies, userAgent):
@@ -27,23 +46,9 @@ class arkReq:
27 46 self.arkCookies = arkCookies
28 47 self.userAgent = userAgent
29 48
30 arkoseURL = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147"
31 startUrl = "https://chatgpt.com/"
32 conversationUrl = "https://chatgpt.com/c/"
33 arkoseRequest: arkReq = None
34 accessToken: str = None
35 cookies: dict = None
36 headers: dict = None
37 proofToken: list = []
38 turnstileToken: str = None
39
40 49 def readHAR():
41 global arkoseRequest, accessToken, proofToken, turnstileToken
42 50 harPath = []
43 chatArks = []
44 accessToken = None
45 cookies = {}
46 for root, dirs, files in os.walk(get_cookies_dir()):
51 for root, _, files in os.walk(get_cookies_dir()):
47 52 for file in files:
48 53 if file.endswith(".har"):
49 54 harPath.append(os.path.join(root, file))
@@ -60,30 +65,27 @@ def readHAR():
60 65 v_headers = get_headers(v)
61 66 try:
62 67 if "openai-sentinel-proof-token" in v_headers:
63 proofToken = json.loads(base64.b64decode(
68 RequestConfig.proof_token = json.loads(base64.b64decode(
64 69 v_headers["openai-sentinel-proof-token"].split("gAAAAAB", 1)[-1].encode()
65 70 ).decode())
66 71 if "openai-sentinel-turnstile-token" in v_headers:
67 turnstileToken = v_headers["openai-sentinel-turnstile-token"]
72 RequestConfig.turnstile_token = v_headers["openai-sentinel-turnstile-token"]
68 73 except Exception as e:
69 74 if debug.logging:
70 75 print(f"Read proof token: {e}")
71 if arkoseURL in v['request']['url']:
72 arkoseRequest = parseHAREntry(v)
73 elif v['request']['url'] == startUrl or v['request']['url'].startswith(conversationUrl):
76 if arkose_url == v['request']['url']:
77 RequestConfig.arkose_request = parseHAREntry(v)
78 elif v['request']['url'] == start_url or v['request']['url'].startswith(conversation_url):
74 79 try:
75 80 match = re.search(r'"accessToken":"(.*?)"', v["response"]["content"]["text"])
76 81 if match:
77 accessToken = match.group(1)
82 RequestConfig.access_token = match.group(1)
78 83 except KeyError:
79 84 continue
80 cookies = {c['name']: c['value'] for c in v['request']['cookies'] if c['name'] != "oai-did"}
81 headers = v_headers
82 if not accessToken:
85 RequestConfig.cookies = {c['name']: c['value'] for c in v['request']['cookies'] if c['name'] != "oai-did"}
86 RequestConfig.headers = v_headers
87 if RequestConfig.access_token is None:
83 88 raise NoValidHarFileError("No accessToken found in .har files")
84 if not chatArks:
85 return cookies, headers
86 return cookies, headers
87 89
88 90 def get_headers(entry) -> dict:
89 91 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(':')}
@@ -147,11 +149,9 @@ def getN() -> str:
147 149 timestamp = str(int(time.time()))
148 150 return base64.b64encode(timestamp.encode()).decode()
149 151
150 async def getArkoseAndAccessToken(proxy: str) -> tuple[str, str, dict, dict]:
151 global arkoseRequest, accessToken, cookies, headers, proofToken, turnstileToken
152 if arkoseRequest is None or accessToken is None:
153 cookies, headers = readHAR()
154 if arkoseRequest is None:
155 return None, accessToken, cookies, headers, proofToken, turnstileToken
156 newReq = genArkReq(arkoseRequest)
157 return await sendRequest(newReq, proxy), accessToken, cookies, headers, proofToken, turnstileToken
152 async def get_request_config(proxy: str) -> RequestConfig:
153 if RequestConfig.arkose_request is None or RequestConfig.access_token is None:
154 readHAR()
155 if RequestConfig.arkose_request is not None:
156 RequestConfig.arkose_token = await sendRequest(genArkReq(RequestConfig.arkose_request), proxy)
157 return RequestConfig
Modified g4f/Provider/openai/proofofwork.py +5 -5
@@ -4,16 +4,16 @@ import json
4 4 import base64
5 5 from datetime import datetime, timezone
6 6
7 def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", user_agent: str = None, proofToken: str = None):
7 def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", user_agent: str = None, proof_token: str = None):
8 8 if not required:
9 9 return
10 10
11 if proofToken is None:
11 if proof_token is None:
12 12 screen = random.choice([3008, 4010, 6000]) * random.choice([1, 2, 4])
13 13 # Get current UTC time
14 14 now_utc = datetime.now(timezone.utc)
15 15 parse_time = now_utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
16 proofToken = [
16 proof_token = [
17 17 screen, parse_time,
18 18 None, 0, user_agent,
19 19 "https://tcr9i.chat.openai.com/v2/35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js",
@@ -26,8 +26,8 @@ def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", u
26 26
27 27 diff_len = len(difficulty)
28 28 for i in range(100000):
29 proofToken[3] = i
30 json_data = json.dumps(proofToken)
29 proof_token[3] = i
30 json_data = json.dumps(proof_token)
31 31 base = base64.b64encode(json_data.encode()).decode()
32 32 hash_value = hashlib.sha3_512((seed + base).encode()).digest()
33 33
Modified g4f/client/__init__.py +2 -3
@@ -247,7 +247,7 @@ class Images:
247 247 """
248 248 Synchronous generate method that runs the async_generate method in an event loop.
249 249 """
250 return asyncio.run(self.async_generate(prompt, model, provider, response_format=response_format, proxy=proxy **kwargs))
250 return asyncio.run(self.async_generate(prompt, model, provider, response_format=response_format, proxy=proxy, **kwargs))
251 251
252 252 async def async_generate(self, prompt: str, model: str = None, provider: ProviderType = None, response_format: str = "url", proxy: str = None, **kwargs) -> ImagesResponse:
253 253 if provider is None:
@@ -343,8 +343,7 @@ class Images:
343 343
344 344 def create_variation(self, image: Union[str, bytes], model: str = None, provider: ProviderType = None, response_format: str = "url", **kwargs) -> ImagesResponse:
345 345 return asyncio.run(self.async_create_variation(
346 image, model, provider, response_format
347 **kwargs
346 image, model, provider, response_format, **kwargs
348 347 ))
349 348
350 349 async def async_create_variation(self, image: Union[str, bytes], model: str = None, provider: ProviderType = None, response_format: str = "url", proxy: str = None, **kwargs) -> ImagesResponse: