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

XFEstudio/gpt4free

Add webview support to OpenaiChat

993c9498
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

5 个文件 +130 -61
Modified g4f/Provider/Bing.py +3 -1
@@ -303,7 +303,7 @@ def create_message(
303 303 struct['arguments'][0]['previousMessages'] = [{
304 304 "author": "user",
305 305 "description": context,
306 "contextType": "WebPage",
306 "contextType": "ClientApp",
307 307 "messageType": "Context",
308 308 "messageId": "discover-web--page-ping-mriduna-----"
309 309 }]
@@ -404,6 +404,8 @@ async def stream_generate(
404 404 image_client = BingCreateImages(cookies, proxy)
405 405 image_response = await image_client.create_async(prompt)
406 406 except Exception as e:
407 if debug.logging:
408 print(f"Bing: Failed to create images: {e}")
407 409 response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
408 410 do_read = False
409 411 if response_txt.startswith(returned_text):
Modified g4f/Provider/needs_auth/OpenaiChat.py +116 -48
@@ -14,6 +14,12 @@ try:
14 14 except ImportError:
15 15 has_arkose_generator = False
16 16
17 try:
18 import webview
19 has_webview = True
20 except ImportError:
21 has_webview = False
22
17 23 try:
18 24 from selenium.webdriver.common.by import By
19 25 from selenium.webdriver.support.ui import WebDriverWait
@@ -25,10 +31,10 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
25 31 from ..helper import get_cookies
26 32 from ...webdriver import get_browser
27 33 from ...typing import AsyncResult, Messages, Cookies, ImageType, Union, AsyncIterator
28 from ...requests import get_args_from_browser
34 from ...requests import get_args_from_browser, raise_for_status
29 35 from ...requests.aiohttp import StreamSession
30 36 from ...image import to_image, to_bytes, ImageResponse, ImageRequest
31 from ...errors import MissingRequirementsError, MissingAuthError
37 from ...errors import MissingRequirementsError, MissingAuthError, ProviderNotWorkingError
32 38 from ... import debug
33 39
34 40 class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
@@ -134,7 +140,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
134 140 }
135 141 # Post the image data to the service and get the image data
136 142 async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response:
137 response.raise_for_status()
143 cls._update_request_args()
144 await raise_for_status(response)
138 145 image_data = {
139 146 **data,
140 147 **await response.json(),
@@ -152,14 +159,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
152 159 "x-ms-blob-type": "BlockBlob"
153 160 }
154 161 ) as response:
155 response.raise_for_status()
162 await raise_for_status(response)
156 163 # Post the file ID to the service and get the download URL
157 164 async with session.post(
158 165 f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded",
159 166 json={},
160 167 headers=headers
161 168 ) as response:
162 response.raise_for_status()
169 cls._update_request_args(session)
170 await raise_for_status(response)
163 171 image_data["download_url"] = (await response.json())["download_url"]
164 172 return ImageRequest(image_data)
165 173
@@ -178,7 +186,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
178 186 if not cls.default_model:
179 187 async with session.get(f"{cls.url}/backend-api/models", headers=headers) as response:
180 188 cls._update_request_args(session)
181 response.raise_for_status()
189 await raise_for_status(response)
182 190 data = await response.json()
183 191 if "categories" in data:
184 192 cls.default_model = data["categories"][-1]["default_model"]
@@ -261,7 +269,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
261 269 file_id = first_part["asset_pointer"].split("file-service://", 1)[1]
262 270 try:
263 271 async with session.get(f"{cls.url}/backend-api/files/{file_id}/download", headers=headers) as response:
264 response.raise_for_status()
272 cls._update_request_args(session)
273 await raise_for_status(response)
265 274 download_url = (await response.json())["download_url"]
266 275 return ImageResponse(download_url, prompt)
267 276 except Exception as e:
@@ -288,6 +297,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
288 297 json={"is_visible": False},
289 298 headers=headers
290 299 ) as response:
300 cls._update_request_args(session)
291 301 ...
292 302
293 303 @classmethod
@@ -337,31 +347,32 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
337 347 if parent_id is None:
338 348 parent_id = str(uuid.uuid4())
339 349
340 # Read api_key from arguments
341 api_key = kwargs["access_token"] if "access_token" in kwargs else api_key
342
343 350 async with StreamSession(
344 351 proxies={"https": proxy},
345 352 impersonate="chrome",
346 353 timeout=timeout
347 354 ) as session:
348 # Read api_key and cookies from cache / browser config
355 api_key = kwargs["access_token"] if "access_token" in kwargs else api_key
349 356 if cls._headers is None or cls._expires is None or time.time() > cls._expires:
350 if api_key is None:
351 # Read api_key from cookies
357 if cls._headers is None:
352 358 cookies = get_cookies("chat.openai.com", False) if cookies is None else cookies
353 359 api_key = cookies["access_token"] if "access_token" in cookies else api_key
354 cls._create_request_args(cookies)
360 if api_key is None:
361 try:
362 await cls.webview_access_token() if has_webview else None
363 except Exception as e:
364 if debug.logging:
365 print(f"Use webview failed: {e}")
355 366 else:
356 367 api_key = cls._api_key if api_key is None else api_key
357 # Read api_key with session cookies
358 #if api_key is None and cookies:
359 # api_key = await cls.fetch_access_token(session, cls._headers)
360 # Load default model
361 if cls.default_model is None and api_key is not None:
368
369 if api_key is not None:
370 cls._create_request_args(cookies)
371 cls._set_api_key(api_key)
372
373 if cls.default_model is None and cls._headers is not None:
362 374 try:
363 375 if not model:
364 cls._set_api_key(api_key)
365 376 cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers))
366 377 else:
367 378 cls.default_model = cls.get_model(model)
@@ -369,8 +380,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
369 380 if debug.logging:
370 381 print("OpenaiChat: Load default_model failed")
371 382 print(f"{e.__class__.__name__}: {e}")
372 # Browse api_key and default model
373 if api_key is None or cls.default_model is None:
383 if cls.default_model is None:
374 384 login_url = os.environ.get("G4F_LOGIN_URL")
375 385 if login_url:
376 386 yield f"Please login: [ChatGPT]({login_url})\n\n"
@@ -379,20 +389,21 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
379 389 except MissingRequirementsError:
380 390 raise MissingAuthError(f'Missing "access_token". Add a "api_key" please')
381 391 cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers))
382 else:
383 cls._set_api_key(api_key)
384 392
385 393 async with session.post(
386 394 f"{cls.url}/backend-api/sentinel/chat-requirements",
387 395 json={"conversation_mode_kind": "primary_assistant"},
388 396 headers=cls._headers
389 397 ) as response:
390 response.raise_for_status()
398 cls._update_request_args(session)
399 await raise_for_status(response)
391 400 data = await response.json()
401 blob = data["arkose"]["dx"]
392 402 need_arkose = data["arkose"]["required"]
393 403 chat_token = data["token"]
394 404
395 405 if need_arkose and not has_arkose_generator:
406 raise ProviderNotWorkingError("OpenAI Plus Subscriber are not working")
396 407 raise MissingRequirementsError('Install "py-arkose-generator" package')
397 408
398 409 try:
@@ -407,6 +418,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
407 418 while fields.finish_reason is None:
408 419 conversation_id = conversation_id if fields.conversation_id is None else fields.conversation_id
409 420 parent_id = parent_id if fields.message_id is None else fields.message_id
421 websocket_request_id = str(uuid.uuid4())
410 422 data = {
411 423 "action": action,
412 424 "conversation_mode": {"kind": "primary_assistant"},
@@ -416,25 +428,29 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
416 428 "parent_message_id": parent_id,
417 429 "model": model,
418 430 "history_and_training_disabled": history_disabled and not auto_continue,
431 "websocket_request_id": websocket_request_id
419 432 }
420 433 if action != "continue":
421 434 messages = messages if conversation_id is None else [messages[-1]]
422 data["messages"] = cls.create_messages(messages, image_request)
435 data["messages"] = cls.create_messages(messages, image_request)
436 headers = {
437 "Accept": "text/event-stream",
438 "OpenAI-Sentinel-Chat-Requirements-Token": chat_token,
439 **cls._headers
440 }
441 if need_arkose:
442 raise ProviderNotWorkingError("OpenAI Plus Subscriber are not working")
443 headers["OpenAI-Sentinel-Arkose-Token"] = await cls.get_arkose_token(session, cls._headers, blob)
444 headers["OpenAI-Sentinel-Chat-Requirements-Token"] = chat_token
423 445
424 446 async with session.post(
425 447 f"{cls.url}/backend-api/conversation",
426 448 json=data,
427 headers={
428 "Accept": "text/event-stream",
429 **({"OpenAI-Sentinel-Arkose-Token": await cls.get_arkose_token(session)} if need_arkose else {}),
430 "OpenAI-Sentinel-Chat-Requirements-Token": chat_token,
431 **cls._headers
432 }
449 headers=headers
433 450 ) as response:
434 451 cls._update_request_args(session)
435 if not response.ok:
436 raise RuntimeError(f"Response {response.status}: {await response.text()}")
437 async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, fields):
452 await raise_for_status(response)
453 async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, fields, websocket_request_id):
438 454 if response_fields:
439 455 response_fields = False
440 456 yield fields
@@ -447,21 +463,35 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
447 463 await cls.delete_conversation(session, cls._headers, fields.conversation_id)
448 464
449 465 @staticmethod
450 async def iter_messages_ws(ws: ClientWebSocketResponse, conversation_id: str) -> AsyncIterator:
466 async def iter_messages_ws(ws: ClientWebSocketResponse, conversation_id: str, is_curl: bool) -> AsyncIterator:
451 467 while True:
452 message = await ws.receive_json()
468 if is_curl:
469 message = json.loads(ws.recv()[0])
470 else:
471 message = await ws.receive_json()
453 472 if message["conversation_id"] == conversation_id:
454 473 yield base64.b64decode(message["body"])
455 474
456 475 @classmethod
457 async def iter_messages_chunk(cls, messages: AsyncIterator, session: StreamSession, fields: ResponseFields) -> AsyncIterator:
476 async def iter_messages_chunk(
477 cls,
478 messages: AsyncIterator,
479 session: StreamSession,
480 fields: ResponseFields
481 ) -> AsyncIterator:
458 482 last_message: int = 0
459 483 async for message in messages:
460 484 if message.startswith(b'{"wss_url":'):
461 485 message = json.loads(message)
462 async with session.ws_connect(message["wss_url"]) as ws:
463 async for chunk in cls.iter_messages_chunk(cls.iter_messages_ws(ws, message["conversation_id"]), session, fields):
486 ws = await session.ws_connect(message["wss_url"])
487 try:
488 async for chunk in cls.iter_messages_chunk(
489 cls.iter_messages_ws(ws, message["conversation_id"], hasattr(ws, "recv")),
490 session, fields
491 ):
464 492 yield chunk
493 finally:
494 await ws.aclose()
465 495 break
466 496 async for chunk in cls.iter_messages_line(session, message, fields):
467 497 if fields.finish_reason is not None:
@@ -513,6 +543,43 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
513 543 if "finish_details" in line["message"]["metadata"]:
514 544 fields.finish_reason = line["message"]["metadata"]["finish_details"]["type"]
515 545
546 @classmethod
547 async def webview_access_token(cls) -> str:
548 window = webview.create_window("OpenAI Chat", cls.url)
549 await asyncio.sleep(3)
550 prompt_input = None
551 while not prompt_input:
552 try:
553 await asyncio.sleep(1)
554 prompt_input = window.dom.get_element("#prompt-textarea")
555 except:
556 ...
557 window.evaluate_js("""
558 this._fetch = this.fetch;
559 this.fetch = async (url, options) => {
560 const response = await this._fetch(url, options);
561 if (url == "https://chat.openai.com/backend-api/conversation") {
562 this._headers = options.headers;
563 return response;
564 }
565 return response;
566 };
567 """)
568 window.evaluate_js("""
569 document.querySelector('.from-token-main-surface-secondary').click();
570 """)
571 headers = None
572 while headers is None:
573 headers = window.evaluate_js("this._headers")
574 await asyncio.sleep(1)
575 headers["User-Agent"] = window.evaluate_js("window.navigator.userAgent")
576 cookies = [list(*cookie.items()) for cookie in window.get_cookies()]
577 window.destroy()
578 cls._cookies = dict([(name, cookie.value) for name, cookie in cookies])
579 cls._headers = headers
580 cls._expires = int(time.time()) + 60 * 60 * 4
581 cls._update_cookie_header()
582
516 583 @classmethod
517 584 def browse_access_token(cls, proxy: str = None, timeout: int = 1200) -> None:
518 585 """
@@ -542,10 +609,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
542 609 cls._update_cookie_header()
543 610 cls._set_api_key(access_token)
544 611 finally:
545 driver.close()
612 driver.close()
546 613
547 614 @classmethod
548 async def get_arkose_token(cls, session: StreamSession) -> str:
615 async def get_arkose_token(cls, session: StreamSession, headers: dict, blob: str) -> str:
549 616 """
550 617 Obtain an Arkose token for the session.
551 618
@@ -559,16 +626,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
559 626 RuntimeError: If unable to retrieve the token.
560 627 """
561 628 config = {
562 "pkey": "3D86FBBA-9D22-402A-B512-3420086BA6CC",
629 "pkey": "35536E1E-65B4-4D96-9D97-6ADB7EFF8147",
563 630 "surl": "https://tcr9i.chat.openai.com",
564 "headers": {
565 "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
566 },
631 "headers": headers,
567 632 "site": cls.url,
633 "data": {"blob": blob}
568 634 }
569 635 args_for_request = get_values_for_request(config)
570 636 async with session.post(**args_for_request) as response:
571 response.raise_for_status()
637 await raise_for_status(response)
572 638 decoded_json = await response.json()
573 639 if "token" in decoded_json:
574 640 return decoded_json["token"]
@@ -591,7 +657,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
591 657
592 658 @classmethod
593 659 def _create_request_args(cls, cookies: Union[Cookies, None]):
594 cls._headers = {}
660 cls._headers = {
661 "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
662 }
595 663 cls._cookies = {} if cookies is None else cookies
596 664 cls._update_cookie_header()
597 665
Modified g4f/gui/client/html/index.html +0 -3
@@ -161,10 +161,7 @@
161 161 <option value="gemini-pro">gemini-pro</option>
162 162 <option value="">----</option>
163 163 </select>
164 </div>
165 <div class="field">
166 164 <select name="model2" id="model2" class="hidden">
167 </select>
168 165 </div>
169 166 <div class="field">
170 167 <select name="jailbreak" id="jailbreak" style="display: none;">
Modified g4f/gui/client/js/chat.v1.js +5 -6
@@ -16,9 +16,12 @@ const providerSelect = document.getElementById("provider");
16 16 const modelSelect = document.getElementById("model");
17 17 const modelProvider = document.getElementById("model2");
18 18 const systemPrompt = document.getElementById("systemPrompt")
19 const jailbreak = document.getElementById("jailbreak");
19 20
20 21 let prompt_lock = false;
21 22
23 const options = ["switch", "model", "model2", "jailbreak", "patch", "provider", "history"];
24
22 25 hljs.addPlugin(new CopyButtonPlugin());
23 26
24 27 messageInput.addEventListener("blur", () => {
@@ -207,9 +210,7 @@ const ask_gpt = async () => {
207 210
208 211 window.scrollTo(0, 0);
209 212 window.controller = new AbortController();
210
211 jailbreak = document.getElementById("jailbreak");
212 window.text = '';
213 window.text = "";
213 214
214 215 stop_generating.classList.remove(`stop_generating-hidden`);
215 216
@@ -246,7 +247,7 @@ const ask_gpt = async () => {
246 247 id: window.token,
247 248 conversation_id: window.conversation_id,
248 249 model: get_selected_model(),
249 jailbreak: jailbreak.options[jailbreak.selectedIndex].value,
250 jailbreak: jailbreak?.options[jailbreak.selectedIndex].value,
250 251 web_search: document.getElementById(`switch`).checked,
251 252 provider: providerSelect.options[providerSelect.selectedIndex].value,
252 253 patch_provider: document.getElementById('patch')?.checked,
@@ -670,8 +671,6 @@ sidebar_button.addEventListener("click", (event) => {
670 671 window.scrollTo(0, 0);
671 672 });
672 673
673 const options = ["switch", "model", "model2", "jailbreak", "patch", "provider", "history"];
674
675 674 const register_settings_localstorage = async () => {
676 675 options.forEach((id) => {
677 676 element = document.getElementById(id);
Modified g4f/requests/__init__.py +6 -3
@@ -81,22 +81,25 @@ def get_session_from_browser(url: str, webdriver: WebDriver = None, proxy: str =
81 81 impersonate="chrome"
82 82 )
83 83
84 def is_cloudflare(text: str):
85 return '<div id="cf-please-wait">' in text or "<title>Just a moment...</title>" in text
86
84 87 async def raise_for_status_async(response: Union[StreamResponse, ClientResponse], message: str = None):
85 88 if response.status in (429, 402):
86 89 raise RateLimitError(f"Response {response.status}: Rate limit reached")
87 90 message = await response.text() if not response.ok and message is None else message
88 if response.status == 403 and "<title>Just a moment...</title>" in message:
91 if response.status == 403 and is_cloudflare(message):
89 92 raise ResponseStatusError(f"Response {response.status}: Cloudflare detected")
90 93 elif not response.ok:
91 94 raise ResponseStatusError(f"Response {response.status}: {message}")
92 95
93 96 def raise_for_status(response: Union[StreamResponse, ClientResponse, Response, RequestsResponse], message: str = None):
94 if isinstance(response, StreamSession) or isinstance(response, ClientResponse):
97 if hasattr(response, "status"):
95 98 return raise_for_status_async(response, message)
96 99
97 100 if response.status_code in (429, 402):
98 101 raise RateLimitError(f"Response {response.status_code}: Rate limit reached")
99 elif response.status_code == 403 and "<title>Just a moment...</title>" in response.text:
102 elif response.status_code == 403 and is_cloudflare(response.text):
100 103 raise ResponseStatusError(f"Response {response.status_code}: Cloudflare detected")
101 104 elif not response.ok:
102 105 raise ResponseStatusError(f"Response {response.status_code}: {response.text if message is None else message}")