返回提交历史
Modified
g4f/Provider/Copilot.py
+2
-2
Modified
g4f/Provider/CopilotSession.py
+6
-3
Modified
g4f/Provider/DeepInfra.py
+26
-29
Modified
g4f/Provider/audio/ElevenLabs.py
+2
-4
Modified
g4f/Provider/glm/captcha_solver.py
+2
-2
Modified
g4f/Provider/needs_auth/Gemini.py
+0
-3
Modified
g4f/Provider/needs_auth/LMArena.py
+7
-12
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+5
-6
Modified
g4f/api/__init__.py
+1
-1
Modified
g4f/gui/server/api.py
+0
-1
Modified
g4f/requests/__init__.py
+0
-5
XFEstudio/gpt4free
Refactor providers to use CDP-based functionality and remove has_nodriver references
da953e78
代码差异
11 个文件
+51
-68
@@ -26,7 +26,7 @@ from ..typing import AsyncResult, Messages, MediaListType
26
26
from ..errors import MissingRequirementsError, NoValidHarFileError, MissingAuthError
27
27
from ..providers.response import *
28
28
from ..tools.media import merge_media
29
from ..requests import get_nodriver, DEFAULT_HEADERS
29
from ..requests import get_nodriver, DEFAULT_HEADERS, has_cdp
30
30
from ..image import to_bytes, is_accepted_format
31
31
from .helper import get_last_user_message
32
32
from ..files import get_bucket_dir
@@ -119,7 +119,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
119
119
access_token, useridentitytype, cookies = readHAR(cls.url)
120
120
except NoValidHarFileError as h:
121
121
debug.log(f"Copilot: {h}")
122
if has_nodriver:
122
if has_cdp:
123
123
yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
124
124
(
125
125
access_token,
@@ -92,9 +92,12 @@ class CopilotSession(AsyncAuthedProvider, ProviderModelMixin):
92
92
await page.send(cdp.network.enable())
93
93
queue = asyncio.Queue()
94
94
95
def handle_ws_message(event):
96
if hasattr(event, "response") and event.response.payload_data:
97
queue.put_nowait((event.request_id, event.response.payload_data))
95
def handle_ws_message(event, **kwargs):
96
response = getattr(event, "response", None)
97
if response is not None:
98
payload_data = getattr(response, "payloadData", None) or response.get("payloadData")
99
if payload_data:
100
queue.put_nowait((event.request_id, payload_data))
98
101
99
102
page.add_handler(cdp.network.WebSocketFrameReceived, handle_ws_message)
100
103
textarea = await page.select("textarea")
@@ -3,27 +3,25 @@ from __future__ import annotations
3
3
import asyncio
4
4
import requests
5
5
6
from ..requests.cdp import SyncCDPSession
6
from ..requests.cdp import CDPSession
7
7
from .. import debug
8
8
from .template import OpenaiTemplate
9
9
10
10
11
def _get_turnstile_token_sync(model: str) -> str:
11
async def _get_turnstile_token_async(model: str) -> str:
12
12
"""
13
Synchronous Turnstile token retrieval using SyncCDPSession with retries.
14
Uses a blocking recv() loop — no async timeouts, waits as long as needed.
15
Designed to be run via asyncio.run_in_executor() from async context.
13
Async Turnstile token retrieval using CDPSession with retries.
14
Navigates to the DeepInfra model page, injects a fetch blocker,
15
enters text, and polls for the Cloudflare Turnstile token.
16
16
"""
17
import time
18
19
17
for attempt in range(1):
20
session = SyncCDPSession()
21
session.start_chrome()
18
session = CDPSession()
19
await session.start()
22
20
23
21
try:
24
22
url = f"https://deepinfra.com/{model}"
25
23
debug.log(f"[DeepInfra] Navigating to {url} (Attempt {attempt + 1}/3)...")
26
session.navigate(url)
24
await session.navigate(url)
27
25
28
26
# Inject completions request blocker
29
27
fetch_blocker_js = """
@@ -36,10 +34,10 @@ def _get_turnstile_token_sync(model: str) -> str:
36
34
return origFetch.apply(this, args);
37
35
};
38
36
"""
39
session.evaluate_js(fetch_blocker_js)
37
await session.evaluate_js(fetch_blocker_js)
40
38
41
39
# Try to click "Accept" on cookies consent popup if present
42
session.evaluate_js(
40
await session.evaluate_js(
43
41
"""
44
42
(() => {
45
43
const btn = Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim() === 'Accept');
@@ -50,14 +48,14 @@ def _get_turnstile_token_sync(model: str) -> str:
50
48
51
49
# Click on an empty page area to give window focus — signals Cloudflare that
52
50
# a real user is present, which speeds up Turnstile token generation significantly.
53
session.click(200, 400)
51
await session.click(200, 400)
54
52
55
53
# Wait for textarea readiness, then focus and input text
56
54
debug.log("[DeepInfra] Waiting for active textarea...")
57
55
text_entered = False
58
56
for _ in range(80): # Up to 40 seconds
59
57
try:
60
ready = session.evaluate_js(
58
ready = await session.evaluate_js(
61
59
"""
62
60
(() => {
63
61
const ta = document.querySelector('textarea');
@@ -79,14 +77,14 @@ def _get_turnstile_token_sync(model: str) -> str:
79
77
)
80
78
81
79
# Retrieve textarea nodeId for native focusing
82
doc = session.call("DOM.getDocument")
80
doc = await session.call("DOM.getDocument")
83
81
root_id = doc["root"]["nodeId"]
84
textarea = session.call(
82
textarea = await session.call(
85
83
"DOM.querySelector", nodeId=root_id, selector="textarea"
86
84
)
87
85
88
86
# Native focus via CDP
89
session.call("DOM.focus", nodeId=textarea["nodeId"])
87
await session.call("DOM.focus", nodeId=textarea["nodeId"])
90
88
91
89
# Enter text via native CDP command
92
90
import random
@@ -102,12 +100,12 @@ def _get_turnstile_token_sync(model: str) -> str:
102
100
"Can you hear me?",
103
101
]
104
102
)
105
session.call("Input.insertText", text=test_prompt)
103
await session.call("Input.insertText", text=test_prompt)
106
104
107
time.sleep(0.5)
105
await asyncio.sleep(0.5)
108
106
109
107
# Simulate Enter keypress
110
session.call(
108
await session.call(
111
109
"Input.dispatchKeyEvent",
112
110
type="keyDown",
113
111
windowsVirtualKeyCode=13,
@@ -116,7 +114,7 @@ def _get_turnstile_token_sync(model: str) -> str:
116
114
text="\r",
117
115
unmodifiedText="\r",
118
116
)
119
session.call(
117
await session.call(
120
118
"Input.dispatchKeyEvent",
121
119
type="keyUp",
122
120
windowsVirtualKeyCode=13,
@@ -130,13 +128,13 @@ def _get_turnstile_token_sync(model: str) -> str:
130
128
break
131
129
except Exception:
132
130
pass
133
time.sleep(0.5)
131
await asyncio.sleep(0.5)
134
132
135
133
if not text_entered:
136
134
debug.log(
137
135
"[DeepInfra] Textarea/Turnstile not ready or failed to submit, retrying attempt..."
138
136
)
139
session.close()
137
await session.close()
140
138
continue
141
139
142
140
# Poll page for Turnstile token
@@ -145,28 +143,27 @@ def _get_turnstile_token_sync(model: str) -> str:
145
143
token = ""
146
144
for i in range(240): # Up to 120 seconds per attempt
147
145
try:
148
token = session.evaluate_js(token_js)
146
token = await session.evaluate_js(token_js)
149
147
if token:
150
148
debug.log(f"[DeepInfra] Token generated on check {i+1}!")
151
149
return token
152
150
except Exception:
153
151
pass
154
time.sleep(0.5)
152
await asyncio.sleep(0.5)
155
153
156
154
except Exception as e:
157
155
debug.log(f"[DeepInfra] Error on attempt {attempt + 1}: {e}")
158
156
finally:
159
session.close()
157
await session.close()
160
158
161
159
return ""
162
160
163
161
164
162
async def get_turnstile_token_async(model: str = None) -> str:
165
"""Run the synchronous Turnstile solver in a thread pool executor."""
163
"""Obtain a Cloudflare Turnstile token for DeepInfra."""
166
164
if not model:
167
165
model = DeepInfra.default_model
168
loop = asyncio.get_running_loop()
169
return await loop.run_in_executor(None, _get_turnstile_token_sync, model)
166
return await _get_turnstile_token_async(model)
170
167
171
168
172
169
class DeepInfra(OpenaiTemplate):
@@ -4,8 +4,6 @@ import os
4
4
import json
5
5
import base64
6
6
7
from ...requests import has_nodriver
8
9
7
from ...typing import AsyncResult, Messages
10
8
from ...providers.response import AudioResponse
11
9
from ...image.copy_images import get_filename, get_media_dir, ensure_media_dir
@@ -35,8 +33,8 @@ class ElevenLabs(AsyncGeneratorProvider, ProviderModelMixin):
35
33
36
34
label = "ElevenLabs TTS"
37
35
url = "https://elevenlabs.io"
38
working = has_nodriver
39
use_nodriver = has_nodriver
36
working = True
37
use_nodriver = True
40
38
needs_auth = True # hcaptcha accessibility cookie
41
39
42
40
model_id = "elevenlabs-tts"
@@ -26,7 +26,7 @@ from typing import Optional
26
26
27
27
from ... import debug
28
28
29
from ...requests import get_nodriver, has_nodriver
29
from ...requests import get_nodriver
30
30
from ...requests.cdp_browser import cdp
31
31
32
32
@@ -330,4 +330,4 @@ def invalidate_captcha_token() -> None:
330
330
331
331
def is_available() -> bool:
332
332
"""Whether the captcha solver can run (requires zendriver)."""
333
return has_nodriver
333
return True
@@ -357,9 +357,6 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
357
357
358
358
@classmethod
359
359
async def login_generator(cls, proxy: str = None) -> AsyncIterator[str]:
360
if not has_nodriver:
361
debug.log("Skip browser login in Gemini provider")
362
return
363
360
browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="gemini")
364
361
try:
365
362
yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
@@ -377,7 +377,7 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
377
377
return
378
378
if isinstance(data, dict):
379
379
json_data = data
380
elif data[0] == "$":
380
elif data and data[0] == "$":
381
381
if data[1] in ["div", "defs", "style", "script"]:
382
382
return
383
383
json_data = data[3]
@@ -405,7 +405,10 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
405
405
continue
406
406
chunk_id, chunk_data = match.groups()
407
407
if chunk_data.startswith("I["):
408
data = json.loads(chunk_data[1:])
408
try:
409
data = json.loads(chunk_data[1:])
410
except json.decoder.JSONDecodeError:
411
continue
409
412
async with StreamSession() as session:
410
413
if "Evaluation" == data[2]:
411
414
js_files = dict(zip(data[1][::2], data[1][1::2]))
@@ -560,12 +563,8 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
560
563
for _ in range(2):
561
564
if args:
562
565
pass
563
elif has_nodriver:
564
args = await cls.get_args_from_nodriver(proxy, _need_clear_cookies)
565
566
else:
566
raise MissingRequirementsError(
567
"No auth file found and browser (CDP) is not available."
568
)
567
args = await cls.get_args_from_nodriver(proxy, _need_clear_cookies)
569
568
570
569
if not cls._models_loaded:
571
570
# change to async
@@ -573,10 +572,6 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
573
572
574
573
def get_mode_id(_model):
575
574
model_id = None
576
# if not model:
577
# model = cls.default_model
578
if _model in cls.model_aliases:
579
_model = cls.model_aliases[_model]
580
575
if _model in cls.text_models:
581
576
model_id = cls.text_models[_model]
582
577
elif _model in cls.image_models:
@@ -609,7 +604,7 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
609
604
userMessageId = str(uuid7())
610
605
modelAMessageId = str(uuid7())
611
606
modelBMessageId = str(uuid7())
612
if not cls._grecaptcha and has_nodriver:
607
if not cls._grecaptcha:
613
608
debug.log("No grecaptcha token found, obtaining new one...")
614
609
args = await cls.get_grecaptcha(args, proxy)
615
610
files = await cls.prepare_images(args, media)
@@ -1352,12 +1352,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
1352
1352
f"Access token is not valid: {cls.request_config.access_token}"
1353
1353
)
1354
1354
except NoValidHarFileError:
1355
if has_nodriver:
1356
if cls.request_config.access_token is None:
1357
yield RequestLogin(
1358
cls.label, os.environ.get("G4F_LOGIN_URL", "")
1359
)
1360
await cls.nodriver_auth(proxy)
1355
if cls.request_config.access_token is None:
1356
yield RequestLogin(
1357
cls.label, os.environ.get("G4F_LOGIN_URL", "")
1358
)
1359
await cls.nodriver_auth(proxy)
1361
1360
else:
1362
1361
raise
1363
1362
@@ -73,7 +73,7 @@ except ImportError:
73
73
pass
74
74
75
75
76
from g4f.requests import has_nodriver, has_cdp
76
from g4f.requests import has_cdp
77
77
78
78
import g4f
79
79
import g4f.debug
@@ -270,7 +270,6 @@ class Api:
270
270
},
271
271
)
272
272
for chunk in result:
273
print(type(chunk))
274
273
if isinstance(chunk, ProviderInfo):
275
274
model = getattr(chunk, "model", model)
276
275
provider = getattr(chunk, "provider", provider)
@@ -41,11 +41,6 @@ try:
41
41
except ImportError:
42
42
has_cdp = False
43
43
44
# CDP-based browser wrapper replaces zendriver/nodriver entirely.
45
# ``has_nodriver`` is kept as an alias for ``has_cdp`` so existing provider
46
# code that checks ``has_nodriver`` continues to work without modification.
47
has_nodriver = has_cdp
48
49
44
from .cdp_browser import (
50
45
CDPBrowser,
51
46
CDPTab,