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

XFEstudio/gpt4free

feat: add image generation support and fix headless bypass to Perchance provider

35fac2bd
kqlio67 <kqlio67@users.noreply.github.com>
提交于

代码差异

1 个文件 +86 -8
Modified g4f/Provider/Perchance.py +86 -8
@@ -3,34 +3,47 @@ from __future__ import annotations
3 3 import asyncio
4 4 import random
5 5 import json
6 from aiohttp import ClientSession
6 7 from g4f.requests.cdp import CDPSession
7 8 from ..typing import AsyncResult, Messages
8 9 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
9 from .helper import format_prompt
10 from .helper import format_prompt, format_media_prompt
10 11 from ..requests import StreamSession
12 from ..providers.response import ImageResponse
11 13
12 14 class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
13 15 url = "https://perchance.org/ai-chat"
14 16 api_endpoint = "https://text-generation.perchance.org/api/generate"
17 image_api_endpoint = "https://image-generation.perchance.org/api/generate"
15 18 verify_url = "https://text-generation.perchance.org/embed?thread=0"
16 19 working = True
17 20 supports_stream = True
18 21 supports_system_message = True
19 22 supports_message_history = True
20 23 default_model = "perchance"
24 default_image_model = "perchance-image"
25 image_models = ["perchance-image"]
26 models = [default_model] + image_models
21 27
22 28 # Class-level cache for credentials to avoid restarting browser for every request
23 29 _user_key: str | None = None
24 30 _cookies: dict | None = None
25 31 _headers: dict | None = None
32
33 _image_user_key: str | None = None
34 _image_cookies: dict | None = None
35 _image_headers: dict | None = None
26 36
27 37 @classmethod
28 async def _get_user_key(cls, proxy: str = None) -> tuple[str, dict, dict]:
38 async def _get_user_key(cls, is_image: bool = False, proxy: str = None) -> tuple[str, dict, dict]:
29 39 """Runs CDP to solve Turnstile and extract the userKey and session cookies."""
30 session = CDPSession(headless=True)
40 session = CDPSession(headless=False)
31 41 await session.start()
32 42 try:
33 await session.navigate(cls.verify_url)
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"
45
46 await session.navigate(verify_url)
34 47
35 48 # Setup message capture in page context
36 49 setup_js = """
@@ -66,19 +79,30 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
66 79 headers = {
67 80 "user-agent": user_agent,
68 81 "Accept": "*/*",
69 "Origin": "https://text-generation.perchance.org",
70 "Referer": "https://text-generation.perchance.org/embed",
82 "Origin": origin,
83 "Referer": verify_url,
71 84 }
72 85
73 86 return user_key, cookies, headers
74 87 finally:
75 88 await session.close()
76 89
90 @classmethod
91 async def get_ad_access_code(cls, proxy: str = None) -> str:
92 cache_bust = random.randint(1000000, 9999999)
93 url = f"https://perchance.org/api/getAccessCodeForAdPoweredStuff?__cacheBust={cache_bust}"
94 async with ClientSession() as session:
95 async with session.get(url, proxy=proxy) as response:
96 if response.status == 200:
97 return await response.text()
98 return ""
99
77 100 @classmethod
78 101 async def create_async_generator(
79 102 cls,
80 103 model: str,
81 104 messages: Messages,
105 prompt: str = None,
82 106 proxy: str = None,
83 107 **kwargs
84 108 ) -> AsyncResult:
@@ -86,7 +110,61 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
86 110 if not cls._user_key or not cls._cookies or not cls._headers:
87 111 cls._user_key, cls._cookies, cls._headers = await cls._get_user_key(proxy=proxy)
88 112
89 # Approximate token count (chars divided by ~4)
113 # Image Generation Flow
114 if model in cls.image_models or kwargs.get("image"):
115 if not cls._image_user_key or not cls._image_cookies or not cls._image_headers:
116 cls._image_user_key, cls._image_cookies, cls._image_headers = await cls._get_user_key(is_image=True, proxy=proxy)
117
118 if not prompt:
119 prompt = format_media_prompt(messages)
120
121 ad_access_code = await cls.get_ad_access_code(proxy=proxy)
122 req_id = random.random()
123
124 payload = {
125 "prompt": prompt,
126 "negativePrompt": "",
127 "seed": -1,
128 "resolution": "512x512",
129 "guidanceScale": 7,
130 "channel": "text-to-image-plugin-example",
131 "subChannel": "public",
132 "userKey": cls._image_user_key,
133 "adAccessCode": ad_access_code,
134 "requestId": str(req_id)
135 }
136
137 gen_url = f"{cls.image_api_endpoint}?userKey={cls._image_user_key}&requestId={req_id}&adAccessCode={ad_access_code}&__cacheBust={random.random()}"
138
139 async with StreamSession(
140 headers=cls._image_headers,
141 cookies=cls._image_cookies,
142 proxies={"all": proxy} if proxy else None,
143 impersonate="chrome"
144 ) as session:
145 async with session.post(gen_url, json=payload, proxy=proxy) as response:
146 if response.status in (401, 403):
147 cls._image_user_key = cls._image_cookies = cls._image_headers = None
148 raise RuntimeError("auth_failed")
149 if response.status != 200:
150 text = await response.text()
151 if "invalid_key" in text or "failed_verification" in text:
152 cls._image_user_key = cls._image_cookies = cls._image_headers = None
153 raise RuntimeError("auth_failed")
154 raise RuntimeError(f"image generation failed: {response.status} {text}")
155
156 data = await response.json()
157 if data.get("status") == "success":
158 image_url = data.get("imageDownloadUrl")
159 if image_url:
160 if not image_url.startswith("http"):
161 image_url = "https://image-generation.perchance.org" + image_url
162 yield ImageResponse(image_url, prompt)
163 return
164 raise RuntimeError(f"Failed to generate image: {data}")
165 return
166
167 # Text Generation Flow
90 168 instruction = format_prompt(messages)
91 169 instruction_token_count = max(1, len(instruction) // 4)
92 170
@@ -139,7 +217,7 @@ class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
139 217 if str(e) == "auth_failed":
140 218 # Clear cached credentials and retry once with fresh verification
141 219 cls._user_key = cls._cookies = cls._headers = None
142 async for chunk in cls.create_async_generator(model, messages, proxy, **kwargs):
220 async for chunk in cls.create_async_generator(model, messages, prompt, proxy, **kwargs):
143 221 yield chunk
144 222 else:
145 223 raise e