返回提交历史
Modified
.github/workflows/publish-workflow.yaml
+10
-8
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+73
-55
Modified
g4f/Provider/openai/har_file.py
+36
-36
Modified
g4f/Provider/openai/proofofwork.py
+5
-5
Modified
g4f/client/__init__.py
+2
-3
XFEstudio/gpt4free
Add full nodriver support to OpenaiChat Move small docker images before old images
2a7770ea
代码差异
5 个文件
+126
-107
@@ -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 }}
@@ -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):
@@ -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
@@ -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
@@ -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: