返回提交历史
Modified
g4f/Provider/Liaobots.py
+9
-2
Added
g4f/Provider/needs_auth/Grok.py
+153
-0
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+44
-33
Modified
g4f/Provider/needs_auth/__init__.py
+1
-0
Modified
g4f/gui/client/demo.html
+2
-0
Modified
g4f/gui/client/static/js/chat.v1.js
+2
-2
Modified
g4f/gui/server/api.py
+3
-1
Modified
g4f/providers/response.py
+2
-2
XFEstudio/gpt4free
Hide request login in api Add Grok provider Update liaobots provider
23814d3c
代码差异
8 个文件
+216
-40
@@ -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:
@@ -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
@@ -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
"""
@@ -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
@@ -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 {
@@ -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)
@@ -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):
@@ -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):