返回提交历史
Modified
g4f/Provider/Bing.py
+3
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+116
-48
Modified
g4f/gui/client/html/index.html
+0
-3
Modified
g4f/gui/client/js/chat.v1.js
+5
-6
Modified
g4f/requests/__init__.py
+6
-3
XFEstudio/gpt4free
Add webview support to OpenaiChat
993c9498
代码差异
5 个文件
+130
-61
@@ -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):
@@ -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
@@ -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;">
@@ -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);
@@ -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}")