返回提交历史
Modified
g4f/Provider/Perchance.py
+20
-9
Modified
g4f/Provider/needs_auth/__init__.py
+1
-0
Modified
g4f/requests/cdp.py
+48
-6
XFEstudio/gpt4free
Fix Perchance API (image generation Turnstile flow & request format) and CDP settings
0427e129
代码差异
3 个文件
+69
-15
@@ -40,8 +40,15 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
40
40
session = CDPSession(headless=False)
41
41
await session.start()
42
42
try:
43
verify_url = "https://image-generation.perchance.org/embed" if is_image else cls.verify_url
44
origin = "https://image-generation.perchance.org" if is_image else "https://text-generation.perchance.org"
43
if is_image:
44
import urllib.parse
45
hash_data = {"prompt": "a", "resolution": "512x512", "guidanceScale": 7, "channel": "ai-text-to-image-generator", "subChannel": "public", "seed": -1, "requestId": "a"}
46
hash_str = urllib.parse.quote(json.dumps(hash_data))
47
verify_url = f"https://image-generation.perchance.org/embed#{hash_str}"
48
origin = "https://image-generation.perchance.org"
49
else:
50
verify_url = cls.verify_url
51
origin = "https://text-generation.perchance.org"
45
52
46
53
await session.navigate(verify_url)
47
54
@@ -57,9 +64,10 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
57
64
await session.evaluate_js(setup_js)
58
65
59
66
# Always trigger verifyUser to ensure Turnstile solving/checking is executed
60
verify_js = """
61
window.postMessage({type: "verifyUser"}, window.location.origin);
62
"""
67
if is_image:
68
verify_js = "if(typeof start === 'function') start({reloadPageOnFail: false});"
69
else:
70
verify_js = 'window.postMessage({type: "verifyUser"}, window.location.origin);'
63
71
await session.evaluate_js(verify_js)
64
72
65
73
# Anti-detect: warm up the browser and disable debugger overhead DURING Turnstile check
@@ -130,22 +138,25 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
130
138
"seed": -1,
131
139
"resolution": "512x512",
132
140
"guidanceScale": 7,
133
"channel": "text-to-image-plugin-example",
141
"channel": "ai-text-to-image-generator",
134
142
"subChannel": "public",
135
143
"userKey": cls._image_user_key,
136
144
"adAccessCode": ad_access_code,
137
145
"requestId": str(req_id)
138
146
}
139
147
140
gen_url = f"{cls.image_api_endpoint}?userKey={cls._image_user_key}&requestId={req_id}&adAccessCode={ad_access_code}&__cacheBust={random.random()}"
148
gen_url = f"{cls.image_api_endpoint}?userKey={cls._image_user_key}&requestId={req_id}&adAccessCode={ad_access_code}&__cacheBust={random.random()}&bdf={random.random()}"
149
150
headers = cls._image_headers.copy()
151
headers["Content-Type"] = "text/plain;charset=UTF-8"
141
152
142
153
async with StreamSession(
143
headers=cls._image_headers,
154
headers=headers,
144
155
cookies=cls._image_cookies,
145
156
proxies={"all": proxy} if proxy else None,
146
157
impersonate="chrome"
147
158
) as session:
148
async with session.post(gen_url, json=payload, proxy=proxy) as response:
159
async with session.post(gen_url, data=json.dumps(payload), proxy=proxy) as response:
149
160
if response.status in (401, 403):
150
161
cls._image_user_key = cls._image_cookies = cls._image_headers = None
151
162
raise RuntimeError("auth_failed")
@@ -12,6 +12,7 @@ from .Cohere import Cohere
12
12
from .CopilotAccount import CopilotAccount
13
13
from .Custom import Custom
14
14
from .Custom import Feature
15
from .DeepSeek import DeepSeek
15
16
from .FenayAI import FenayAI
16
17
from .Gemini import Gemini
17
18
from .GeminiPro import GeminiPro
@@ -210,8 +210,7 @@ def get_shared_browser(host: str, preferred_port: int, headless: bool = True) ->
210
210
chrome_path,
211
211
f"--remote-debugging-port={port}",
212
212
f"--user-data-dir={user_data_dir}",
213
"--window-position=-2000,-2000",
214
"--window-size=1024,768",
213
"--window-size=1920,1080",
215
214
"--no-default-browser-check",
216
215
"--disable-suggestions-ui",
217
216
"--no-first-run",
@@ -219,6 +218,7 @@ def get_shared_browser(host: str, preferred_port: int, headless: bool = True) ->
219
218
"--disable-popup-blocking",
220
219
"--hide-crash-restore-bubble",
221
220
"--disable-features=PrivacySandboxSettings4",
221
"--disable-blink-features=AutomationControlled",
222
222
"--remote-allow-origins=*"
223
223
]
224
224
if headless:
@@ -497,6 +497,42 @@ class CDPSession:
497
497
await asyncio.sleep(delay)
498
498
await self.call("Input.dispatchMouseEvent", type="mouseReleased", button="left", clickCount=1, x=x, y=y)
499
499
500
async def click_turnstile_checkbox(self) -> bool:
501
"""Find the Cloudflare Turnstile iframe on the page and click its center."""
502
js_code = """
503
(() => {
504
const iframes = document.querySelectorAll('iframe');
505
let cfIframe = null;
506
for (let iframe of iframes) {
507
if (iframe.src && iframe.src.includes('challenges.cloudflare.com')) {
508
cfIframe = iframe;
509
break;
510
}
511
}
512
if (!cfIframe) return null;
513
514
const rect = cfIframe.getBoundingClientRect();
515
return {
516
x: rect.left + window.scrollX,
517
y: rect.top + window.scrollY,
518
width: rect.width,
519
height: rect.height
520
};
521
})()
522
"""
523
try:
524
rect = await self.evaluate_js(js_code)
525
if rect and isinstance(rect, dict) and rect.get('width', 0) > 0:
526
# Center of the Turnstile checkbox (usually left aligned in the iframe)
527
center_x = int(rect['x'] + rect['width'] / 4)
528
center_y = int(rect['y'] + rect['height'] / 2)
529
530
await self.click(center_x, center_y)
531
return True
532
except Exception as e:
533
logger.debug(f"Failed to auto-click Turnstile: {e}")
534
return False
535
500
536
async def bypass_turnstile(self):
501
537
"""Execute a sequence of anti-detect actions to bypass Cloudflare Turnstile."""
502
538
import random
@@ -518,19 +554,25 @@ class CDPSession:
518
554
await self.mouse_move(int(x), int(y))
519
555
await asyncio.sleep(random.uniform(0.05, 0.1))
520
556
521
# 3. Click randomly to gain focus
522
await self.click(end_x, end_y)
557
# 3. Try to click the specific Cloudflare Turnstile checkbox
558
clicked_cf = await self.click_turnstile_checkbox()
523
559
524
# 4. Scroll down slightly
560
# 4. If Cloudflare iframe not found, click randomly to gain focus
561
if not clicked_cf:
562
await self.click(end_x, end_y)
563
564
# 5. Scroll down slightly
525
565
await self.evaluate_js(f"window.scrollBy(0, {random.randint(100, 300)})")
526
566
await asyncio.sleep(0.2)
527
567
528
# 5. Temporarily disable Network interception to hide debugger overhead
568
# 6. Temporarily disable Network and Runtime interception to hide debugger overhead
529
569
try:
530
570
await self.call("Network.disable")
571
await self.call("Runtime.disable")
531
572
await asyncio.sleep(2)
532
573
finally:
533
574
await self.call("Network.enable")
575
await self.call("Runtime.enable")
534
576
535
577
async def close(self):
536
578
"""Close WebSocket session and close the specific target tab."""