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

XFEstudio/gpt4free

Hide request login in api Add Grok provider Update liaobots provider

23814d3c
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

8 个文件 +216 -40
Modified g4f/Provider/Liaobots.py +9 -2
@@ -1,6 +1,7 @@
1 1 from __future__ import annotations
2 2
3 3 import uuid
4 import json
4 5 from aiohttp import ClientSession, BaseConnector
5 6
6 7 from ..typing import AsyncResult, Messages
@@ -290,7 +291,10 @@ class Liaobots(AsyncGeneratorProvider, ProviderModelMixin):
290 291 if b"<html coupert-item=" in chunk:
291 292 raise RuntimeError("Invalid session")
292 293 if chunk:
293 yield chunk.decode(errors="ignore")
294 if chunk.startswith(b"data: "):
295 yield json.loads(chunk[6:]).get("content")
296 else:
297 yield chunk.decode(errors="ignore")
294 298 except:
295 299 async with session.post(
296 300 "https://liaobots.work/api/user",
@@ -313,7 +317,10 @@ class Liaobots(AsyncGeneratorProvider, ProviderModelMixin):
313 317 if b"<html coupert-item=" in chunk:
314 318 raise RuntimeError("Invalid session")
315 319 if chunk:
316 yield chunk.decode(errors="ignore")
320 if chunk.startswith(b"data: "):
321 yield json.loads(chunk[6:]).get("content")
322 else:
323 yield chunk.decode(errors="ignore")
317 324
318 325 @classmethod
319 326 async def initialize_auth_code(cls, session: ClientSession) -> None:
Added g4f/Provider/needs_auth/Grok.py +153 -0
@@ -0,0 +1,153 @@
1 import os
2 import json
3 import random
4 import re
5 import base64
6 import asyncio
7 import time
8 from urllib.parse import quote_plus, unquote_plus
9 from pathlib import Path
10 from aiohttp import ClientSession, BaseConnector
11 from typing import Dict, Any, Optional, AsyncIterator, List
12
13 from ... import debug
14 from ...typing import Messages, Cookies, ImagesType, AsyncResult
15 from ...providers.response import JsonConversation, Reasoning, ImagePreview, ImageResponse, TitleGeneration
16 from ...requests.raise_for_status import raise_for_status
17 from ...requests.aiohttp import get_connector
18 from ...requests import get_nodriver
19 from ...errors import MissingAuthError
20 from ...cookies import get_cookies_dir
21 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
22 from ..helper import format_prompt, get_cookies, get_last_user_message
23
24 class Conversation(JsonConversation):
25 def __init__(self,
26 conversation_id: str,
27 response_id: str,
28 choice_id: str,
29 model: str
30 ) -> None:
31 self.conversation_id = conversation_id
32 self.response_id = response_id
33 self.choice_id = choice_id
34 self.model = model
35
36 class Grok(AsyncGeneratorProvider, ProviderModelMixin):
37 label = "Grok AI"
38 url = "https://grok.com"
39 assets_url = "https://assets.grok.com"
40 conversation_url = "https://grok.com/rest/app-chat/conversations"
41
42 needs_auth = True
43 working = False
44
45 default_model = "grok-3"
46 models = [default_model, "grok-3-thinking", "grok-2"]
47
48 _cookies: Cookies = None
49
50 @classmethod
51 async def _prepare_payload(cls, model: str, message: str) -> Dict[str, Any]:
52 return {
53 "temporary": False,
54 "modelName": "grok-latest" if model == "grok-2" else "grok-3",
55 "message": message,
56 "fileAttachments": [],
57 "imageAttachments": [],
58 "disableSearch": False,
59 "enableImageGeneration": True,
60 "returnImageBytes": False,
61 "returnRawGrokInXaiRequest": False,
62 "enableImageStreaming": True,
63 "imageGenerationCount": 2,
64 "forceConcise": False,
65 "toolOverrides": {},
66 "enableSideBySide": True,
67 "isPreset": False,
68 "sendFinalMetadata": True,
69 "customInstructions": "",
70 "deepsearchPreset": "",
71 "isReasoning": model.endswith("-thinking"),
72 }
73
74 @classmethod
75 async def create_async_generator(
76 cls,
77 model: str,
78 messages: Messages,
79 proxy: str = None,
80 cookies: Cookies = None,
81 connector: BaseConnector = None,
82 images: ImagesType = None,
83 return_conversation: bool = False,
84 conversation: Optional[Conversation] = None,
85 **kwargs
86 ) -> AsyncResult:
87 cls._cookies = cookies or cls._cookies or get_cookies(".grok.com", False, True)
88 if not cls._cookies:
89 raise MissingAuthError("Missing required cookies")
90
91 prompt = format_prompt(messages) if conversation is None else get_last_user_message(messages)
92 base_connector = get_connector(connector, proxy)
93
94 headers = {
95 "accept": "*/*",
96 "accept-language": "en-GB,en;q=0.9",
97 "content-type": "application/json",
98 "origin": "https://grok.com",
99 "priority": "u=1, i",
100 "referer": "https://grok.com/",
101 "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126", "Brave";v="126"',
102 "sec-ch-ua-mobile": "?0",
103 "sec-ch-ua-platform": '"macOS"',
104 "sec-fetch-dest": "empty",
105 "sec-fetch-mode": "cors",
106 "sec-fetch-site": "same-origin",
107 "sec-gpc": "1",
108 "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
109 }
110
111 async with ClientSession(
112 headers=headers,
113 cookies=cls._cookies,
114 connector=base_connector
115 ) as session:
116 payload = await cls._prepare_payload(model, prompt)
117 response = await session.post(f"{cls.conversation_url}/new", json=payload)
118 await raise_for_status(response)
119
120 thinking_duration = None
121 async for line in response.content:
122 if line:
123 try:
124 json_data = json.loads(line)
125 result = json_data.get("result", {})
126 response_data = result.get("response", {})
127 image = response_data.get("streamingImageGenerationResponse", None)
128 if image is not None:
129 yield ImagePreview(f'{cls.assets_url}/{image["imageUrl"]}', "", {"cookies": cookies, "headers": headers})
130 token = response_data.get("token", "")
131 is_thinking = response_data.get("isThinking", False)
132 if token:
133 if is_thinking:
134 if thinking_duration is None:
135 thinking_duration = time.time()
136 yield Reasoning(status="🤔 Is thinking...")
137 yield Reasoning(token)
138 else:
139 if thinking_duration is not None:
140 thinking_duration = time.time() - thinking_duration
141 status = f"Thought for {thinking_duration:.2f}s" if thinking_duration > 1 else "Finished"
142 thinking_duration = None
143 yield Reasoning(status=status)
144 yield token
145 generated_images = response_data.get("modelResponse", {}).get("generatedImageUrls", None)
146 if generated_images:
147 yield ImageResponse([f'{cls.assets_url}/{image}' for image in generated_images], "", {"cookies": cookies, "headers": headers})
148 title = response_data.get("title", {}).get("newTitle", "")
149 if title:
150 yield TitleGeneration(title)
151
152 except json.JSONDecodeError:
153 continue
Modified g4f/Provider/needs_auth/OpenaiChat.py +44 -33
@@ -104,6 +104,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
104 104 synthesize_content_type = "audio/aac"
105 105 request_config = RequestConfig()
106 106
107 _api_key: str = None
108 _headers: dict = None
109 _cookies: Cookies = None
107 110 _expires: int = None
108 111
109 112 @classmethod
@@ -111,9 +114,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
111 114 async for chunk in cls.login(proxy=proxy):
112 115 yield chunk
113 116 yield AuthResult(
114 api_key=cls.request_config.access_token,
115 cookies=cls.request_config.cookies or {},
116 headers=cls.request_config.headers or cls.get_default_headers(),
117 api_key=cls._api_key,
118 cookies=cls._cookies or cls.request_config.cookies or {},
119 headers=cls._headers or cls.request_config.headers or cls.get_default_headers(),
117 120 expires=cls._expires,
118 121 proof_token=cls.request_config.proof_token,
119 122 turnstile_token=cls.request_config.turnstile_token
@@ -303,17 +306,17 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
303 306 ) as session:
304 307 image_requests = None
305 308 if not cls.needs_auth:
306 if not cls.request_config.headers:
307 cls._create_request_args(cls.request_config.cookies)
309 if cls._headers is None:
310 cls._create_request_args(cls._cookies)
308 311 async with session.get(cls.url, headers=INIT_HEADERS) as response:
309 312 cls._update_request_args(auth_result, session)
310 313 await raise_for_status(response)
311 314 else:
312 if not cls.request_config.headers and getattr(auth_result, "cookies", None):
315 if cls._headers is None and getattr(auth_result, "cookies", None):
313 316 cls._create_request_args(auth_result.cookies, auth_result.headers)
314 317 if not cls._set_api_key(getattr(auth_result, "api_key", None)):
315 318 raise MissingAuthError("Access token is not valid")
316 async with session.get(cls.url, headers=cls.request_config.headers) as response:
319 async with session.get(cls.url, headers=cls._headers) as response:
317 320 cls._update_request_args(auth_result, session)
318 321 await raise_for_status(response)
319 322 try:
@@ -328,20 +331,20 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
328 331 conversation = copy(conversation)
329 332 if getattr(auth_result, "cookies", {}).get("oai-did") != getattr(conversation, "user_id", None):
330 333 conversation = Conversation(None, str(uuid.uuid4()))
331 if cls.request_config.access_token is None:
334 if cls._api_key is None:
332 335 auto_continue = False
333 336 conversation.finish_reason = None
334 337 sources = Sources([])
335 338 while conversation.finish_reason is None:
336 339 async with session.post(
337 340 f"{cls.url}/backend-anon/sentinel/chat-requirements"
338 if cls.request_config.access_token is None else
341 if cls._api_key is None else
339 342 f"{cls.url}/backend-api/sentinel/chat-requirements",
340 343 json={"p": None if not getattr(auth_result, "proof_token", None) else get_requirements_token(getattr(auth_result, "proof_token", None))},
341 headers=cls.request_config.headers
344 headers=cls._headers
342 345 ) as response:
343 346 if response.status in (401, 403):
344 auth_result.reset()
347 raise MissingAuthError(f"Response status: {response.status}")
345 348 else:
346 349 cls._update_request_args(auth_result, session)
347 350 await raise_for_status(response)
@@ -404,7 +407,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
404 407 messages = messages if conversation_id is None else [messages[-1]]
405 408 data["messages"] = cls.create_messages(messages, image_requests, ["search"] if web_search else None)
406 409 headers = {
407 **cls.request_config.headers,
410 **cls._headers,
408 411 "accept": "text/event-stream",
409 412 "content-type": "application/json",
410 413 "openai-sentinel-chat-requirements-token": chat_token,
@@ -417,7 +420,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
417 420 headers['openai-sentinel-turnstile-token'] = auth_result.turnstile_token
418 421 async with session.post(
419 422 f"{cls.url}/backend-anon/conversation"
420 if cls.request_config.access_token is None else
423 if cls._api_key is None else
421 424 f"{cls.url}/backend-api/conversation",
422 425 json=data,
423 426 headers=headers
@@ -547,7 +550,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
547 550 async with session.get(
548 551 f"{cls.url}/backend-api/synthesize",
549 552 params=params,
550 headers=cls.request_config.headers
553 headers=cls._headers
551 554 ) as response:
552 555 await raise_for_status(response)
553 556 async for chunk in response.iter_content():
@@ -557,25 +560,33 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
557 560 async def login(
558 561 cls,
559 562 proxy: str = None,
563 api_key: str = None,
564 proof_token: str = None,
565 cookies: Cookies = None,
566 headers: dict = None,
560 567 **kwargs
561 568 ) -> AsyncIterator:
562 569 if cls._expires is not None and (cls._expires - 60*10) < time.time():
563 cls.request_config.headers = cls.request_config.access_token = None
564 if cls.request_config.headers is None:
565 cls.request_config.headers = {}
566 if cls.request_config.access_token is not None:
570 cls._headers = cls._api_key = None
571 if cls._headers is None or headers is not None:
572 cls._headers = {} if headers is None else headers
573 if proof_token is not None:
574 cls.request_config.proof_token = proof_token
575 if cookies is not None:
576 cls.request_config.cookies = cookies
577 if api_key is not None:
567 578 cls._create_request_args(cls.request_config.cookies, cls.request_config.headers)
568 cls._set_api_key(cls.request_config.access_token)
579 cls._set_api_key(api_key)
569 580 else:
570 581 try:
571 cls.request_config = await get_request_config(cls.request_config, proxy)
582 await get_request_config(cls.request_config, proxy)
572 583 cls._create_request_args(cls.request_config.cookies, cls.request_config.headers)
573 584 if cls.request_config.access_token is not None or cls.needs_auth:
574 585 if not cls._set_api_key(cls.request_config.access_token):
575 586 raise NoValidHarFileError(f"Access token is not valid: {cls.request_config.access_token}")
576 587 except NoValidHarFileError:
577 588 if has_nodriver:
578 if cls.request_config.access_token is None:
589 if cls._api_key is None:
579 590 yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
580 591 await cls.nodriver_auth(proxy)
581 592 else:
@@ -598,7 +609,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
598 609 if "OpenAI-Sentinel-Turnstile-Token" in event.request.headers:
599 610 cls.request_config.turnstile_token = event.request.headers["OpenAI-Sentinel-Turnstile-Token"]
600 611 if "Authorization" in event.request.headers:
601 cls.request_config.access_token = event.request.headers["Authorization"].split()[-1]
612 cls._api_key = event.request.headers["Authorization"].split()[-1]
602 613 elif event.request.url == arkose_url:
603 614 cls.request_config.arkose_request = arkReq(
604 615 arkURL=event.request.url,
@@ -615,13 +626,13 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
615 626 await page.evaluate("document.getElementById('prompt-textarea').innerText = 'Hello'")
616 627 await page.evaluate("document.querySelector('[data-testid=\"send-button\"]').click()")
617 628 while True:
618 if cls.request_config.access_token is not None or not cls.needs_auth:
629 if cls._api_key is not None or not cls.needs_auth:
619 630 break
620 631 body = await page.evaluate("JSON.stringify(window.__remixContext)")
621 632 if body:
622 633 match = re.search(r'"accessToken":"(.*?)"', body)
623 634 if match:
624 cls.request_config.access_token = match.group(1)
635 cls._api_key = match.group(1)
625 636 break
626 637 await asyncio.sleep(1)
627 638 while True:
@@ -632,7 +643,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
632 643 cls.request_config.cookies = await page.send(get_cookies([cls.url]))
633 644 await page.close()
634 645 cls._create_request_args(cls.request_config.cookies, cls.request_config.headers, user_agent=user_agent)
635 cls._set_api_key(cls.request_config.access_token)
646 cls._set_api_key(cls._api_key)
636 647 finally:
637 648 stop_browser()
638 649
@@ -645,10 +656,10 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
645 656
646 657 @classmethod
647 658 def _create_request_args(cls, cookies: Cookies = None, headers: dict = None, user_agent: str = None):
648 cls.request_config.headers = cls.get_default_headers() if headers is None else headers
659 cls._headers = cls.get_default_headers() if headers is None else headers
649 660 if user_agent is not None:
650 cls.request_config.headers["user-agent"] = user_agent
651 cls.request_config.cookies = {} if cookies is None else cookies
661 cls._headers["user-agent"] = user_agent
662 cls._cookies = {} if cookies is None else cookies
652 663 cls._update_cookie_header()
653 664
654 665 @classmethod
@@ -656,7 +667,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
656 667 if hasattr(auth_result, "cookies"):
657 668 for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
658 669 auth_result.cookies[getattr(c, "key", getattr(c, "name", ""))] = c.value
659 cls.request_config.cookies = auth_result.cookies
670 cls._cookies = auth_result.cookies
660 671 cls._update_cookie_header()
661 672
662 673 @classmethod
@@ -669,15 +680,15 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
669 680 if time.time() > cls._expires:
670 681 debug.log(f"OpenaiChat: API key is expired")
671 682 else:
672 cls.request_config.access_token = api_key
673 cls.request_config.headers["authorization"] = f"Bearer {api_key}"
683 cls._api_key = api_key
684 cls._headers["authorization"] = f"Bearer {api_key}"
674 685 return True
675 686 return False
676 687
677 688 @classmethod
678 689 def _update_cookie_header(cls):
679 if cls.request_config.cookies:
680 cls.request_config.headers["cookie"] = format_cookies(cls.request_config.cookies)
690 if cls._cookies:
691 cls._headers["cookie"] = format_cookies(cls._cookies)
681 692
682 693 class Conversation(JsonConversation):
683 694 """
Modified g4f/Provider/needs_auth/__init__.py +1 -0
@@ -13,6 +13,7 @@ from .GeminiPro import GeminiPro
13 13 from .GigaChat import GigaChat
14 14 from .GithubCopilot import GithubCopilot
15 15 from .GlhfChat import GlhfChat
16 from .Grok import Grok
16 17 from .Groq import Groq
17 18 from .MetaAI import MetaAI
18 19 from .MetaAIAccount import MetaAIAccount
Modified g4f/gui/client/demo.html +2 -0
@@ -105,6 +105,8 @@
105 105 height: 100%;
106 106 position: absolute;
107 107 z-index: -1;
108 object-fit: contain;
109 width: 100%;
108 110 }
109 111
110 112 .description, form p a {
Modified g4f/gui/client/static/js/chat.v1.js +2 -2
@@ -465,7 +465,7 @@ const handle_ask = async (do_ask_gpt = true) => {
465 465 await ask_gpt(message_id);
466 466 }
467 467 } else {
468 await lazy_scroll_to_bottom();
468 await safe_load_conversation(window.conversation_id, true);
469 469 }
470 470 };
471 471
@@ -2490,7 +2490,7 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
2490 2490 } else if (ressource == "conversation") {
2491 2491 let body = JSON.stringify(args);
2492 2492 headers.accept = 'text/event-stream';
2493 if (files !== null) {
2493 if (files.length > 0) {
2494 2494 const formData = new FormData();
2495 2495 for (const file of files) {
2496 2496 formData.append('files', file)
Modified g4f/gui/server/api.py +3 -1
@@ -189,6 +189,8 @@ class Api:
189 189 logger.exception(chunk)
190 190 debug.error(chunk)
191 191 yield self._format_json('message', get_error_message(chunk), error=type(chunk).__name__)
192 elif isinstance(chunk, RequestLogin):
193 yield self._format_json("preview", chunk.to_string())
192 194 elif isinstance(chunk, PreviewResponse):
193 195 yield self._format_json("preview", chunk.to_string())
194 196 elif isinstance(chunk, ImagePreview):
@@ -197,7 +199,7 @@ class Api:
197 199 images = chunk
198 200 if download_images or chunk.get("cookies"):
199 201 chunk.alt = format_image_prompt(kwargs.get("messages"), chunk.alt)
200 images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy=proxy, alt=chunk.alt))
202 images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt))
201 203 images = ImageResponse(images, chunk.alt)
202 204 yield self._format_json("content", str(images), images=chunk.get_list(), alt=chunk.alt)
203 205 elif isinstance(chunk, SynthesizeData):
Modified g4f/providers/response.py +2 -2
@@ -187,12 +187,12 @@ class SynthesizeData(HiddenResponse, JsonMixin):
187 187 self.provider = provider
188 188 self.data = data
189 189
190 class RequestLogin(ResponseType):
190 class RequestLogin(HiddenResponse):
191 191 def __init__(self, label: str, login_url: str) -> None:
192 192 self.label = label
193 193 self.login_url = login_url
194 194
195 def __str__(self) -> str:
195 def to_string(self) -> str:
196 196 return format_link(self.login_url, f"[Login to {self.label}]") + "\n\n"
197 197
198 198 class ImageResponse(ResponseType):