返回提交历史
Modified
g4f/Provider/Bing.py
+84
-55
Modified
g4f/Provider/bing/conversation.py
+7
-27
Modified
g4f/Provider/bing/upload_image.py
+5
-7
Modified
g4f/cookies.py
+1
-1
Modified
g4f/gui/client/js/chat.v1.js
+5
-1
Modified
g4f/providers/helper.py
+7
-5
Modified
g4f/requests/__init__.py
+12
-14
Modified
g4f/webdriver.py
+2
-3
XFEstudio/gpt4free
Add count chars to gui, Add retry support to fix rate limit in Bing
5bcf21f9
代码差异
8 个文件
+123
-113
@@ -4,16 +4,20 @@ import random
4
4
import json
5
5
import uuid
6
6
import time
7
import asyncio
7
8
from urllib import parse
8
from aiohttp import ClientSession, ClientTimeout, BaseConnector
9
from datetime import datetime
10
from aiohttp import ClientSession, ClientTimeout, BaseConnector, WSMsgType
9
11
10
12
from ..typing import AsyncResult, Messages, ImageType, Cookies
11
13
from ..image import ImageResponse, ImageRequest
14
from ..errors import ResponseStatusError
12
15
from .base_provider import AsyncGeneratorProvider
13
from .helper import get_connector
16
from .helper import get_connector, get_random_hex
14
17
from .bing.upload_image import upload_image
15
18
from .bing.create_images import create_images
16
19
from .bing.conversation import Conversation, create_conversation, delete_conversation
20
from .. import debug
17
21
18
22
class Tones:
19
23
"""
@@ -65,8 +69,6 @@ class Bing(AsyncGeneratorProvider):
65
69
prompt = messages[-1]["content"]
66
70
context = create_context(messages[:-1])
67
71
68
cookies = {**get_default_cookies(), **cookies} if cookies else get_default_cookies()
69
70
72
gpt4_turbo = True if model.startswith("gpt-4-turbo") else False
71
73
72
74
return stream_generate(prompt, tone, image, context, cookies, get_connector(connector, proxy, True), web_search, gpt4_turbo, timeout)
@@ -86,6 +88,24 @@ def create_context(messages: Messages) -> str:
86
88
def get_ip_address() -> str:
87
89
return f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
88
90
91
def get_default_cookies():
92
return {
93
'SRCHD' : 'AF=NOFORM',
94
'PPLState' : '1',
95
'KievRPSSecAuth': '',
96
'SUID' : '',
97
'SRCHUSR' : '',
98
'SRCHHPGUSR' : f'HV={int(time.time())}',
99
}
100
101
def create_headers(cookies: Cookies = None) -> dict:
102
if cookies is None:
103
cookies = get_default_cookies()
104
headers = Defaults.headers.copy()
105
headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
106
headers["x-forwarded-for"] = get_ip_address()
107
return headers
108
89
109
class Defaults:
90
110
"""
91
111
Default settings and configurations for the Bing provider.
@@ -169,37 +189,26 @@ class Defaults:
169
189
}
170
190
171
191
# Default headers for requests
192
home = 'https://www.bing.com/chat?q=Bing+AI&FORM=hpcodx'
172
193
headers = {
173
'accept': '*/*',
174
'accept-language': 'en-US,en;q=0.9',
175
'cache-control': 'max-age=0',
176
'sec-ch-ua': '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
177
'sec-ch-ua-arch': '"x86"',
178
'sec-ch-ua-bitness': '"64"',
179
'sec-ch-ua-full-version': '"110.0.1587.69"',
180
'sec-ch-ua-full-version-list': '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
194
'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
181
195
'sec-ch-ua-mobile': '?0',
196
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
197
'sec-ch-ua-arch': '"x86"',
198
'sec-ch-ua-full-version': '"122.0.6261.69"',
199
'accept': 'application/json',
200
'sec-ch-ua-platform-version': '"15.0.0"',
201
"x-ms-client-request-id": str(uuid.uuid4()),
202
'sec-ch-ua-full-version-list': '"Chromium";v="122.0.6261.69", "Not(A:Brand";v="24.0.0.0", "Google Chrome";v="122.0.6261.69"',
203
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.3 OS/Windows',
182
204
'sec-ch-ua-model': '""',
183
205
'sec-ch-ua-platform': '"Windows"',
184
'sec-ch-ua-platform-version': '"15.0.0"',
185
'sec-fetch-dest': 'document',
186
'sec-fetch-mode': 'navigate',
187
'sec-fetch-site': 'none',
188
'sec-fetch-user': '?1',
189
'upgrade-insecure-requests': '1',
190
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69',
191
'x-edge-shopping-flag': '1',
192
'x-forwarded-for': get_ip_address(),
193
}
194
195
def get_default_cookies():
196
return {
197
'SRCHD' : 'AF=NOFORM',
198
'PPLState' : '1',
199
'KievRPSSecAuth': '',
200
'SUID' : '',
201
'SRCHUSR' : '',
202
'SRCHHPGUSR' : f'HV={int(time.time())}',
206
'sec-fetch-site': 'same-origin',
207
'sec-fetch-mode': 'cors',
208
'sec-fetch-dest': 'empty',
209
'referer': home,
210
'accept-encoding': 'gzip, deflate, br',
211
'accept-language': 'en-US,en;q=0.9',
203
212
}
204
213
205
214
def format_message(msg: dict) -> str:
@@ -234,8 +243,6 @@ def create_message(
234
243
"""
235
244
236
245
options_sets = []
237
if not web_search:
238
options_sets.append("nosearchall")
239
246
if gpt4_turbo:
240
247
options_sets.append("dlgpt4t")
241
248
@@ -249,7 +256,7 @@ def create_message(
249
256
"verbosity": "verbose",
250
257
"scenario": "SERP",
251
258
"plugins": [{"id": "c310c353-b9f0-4d76-ab0d-1dd5e979cf68", "category": 1}] if web_search else [],
252
"traceId": str(uuid.uuid4()),
259
"traceId": get_random_hex(40),
253
260
"conversationHistoryOptionsSets": ["autosave","savemem","uprofupd","uprofgen"],
254
261
"gptId": "copilot",
255
262
"isStartOfSession": True,
@@ -257,7 +264,7 @@ def create_message(
257
264
"message":{
258
265
**Defaults.location,
259
266
"userIpAddress": get_ip_address(),
260
"timestamp": "2024-03-11T22:40:36+01:00",
267
"timestamp": datetime.now().isoformat(),
261
268
"author": "user",
262
269
"inputMethod": "Keyboard",
263
270
"text": prompt,
@@ -266,6 +273,7 @@ def create_message(
266
273
"messageId": request_id
267
274
},
268
275
"tone": tone,
276
"extraExtensionParameters": {"gpt-creator-persona": {"personaId": "copilot"}},
269
277
"spokenTextMode": "None",
270
278
"conversationId": conversation.conversationId,
271
279
"participant": {"id": conversation.clientId}
@@ -301,7 +309,10 @@ async def stream_generate(
301
309
connector: BaseConnector = None,
302
310
web_search: bool = False,
303
311
gpt4_turbo: bool = False,
304
timeout: int = 900
312
timeout: int = 900,
313
conversation: Conversation = None,
314
max_retries: int = 5,
315
sleep_retry: int = 15
305
316
):
306
317
"""
307
318
Asynchronously streams generated responses from the Bing API.
@@ -316,20 +327,30 @@ async def stream_generate(
316
327
:param timeout: Timeout for the request.
317
328
:return: An asynchronous generator yielding responses.
318
329
"""
319
headers = Defaults.headers
320
if cookies:
321
headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
330
headers = create_headers(cookies)
322
331
async with ClientSession(
323
headers=headers, cookies=cookies,
324
332
timeout=ClientTimeout(total=timeout), connector=connector
325
333
) as session:
326
conversation = await create_conversation(session)
327
image_request = await upload_image(session, image, tone) if image else None
328
try:
334
while conversation is None:
335
do_read = True
336
try:
337
conversation = await create_conversation(session, headers)
338
except ResponseStatusError as e:
339
max_retries -= 1
340
if max_retries < 1:
341
raise e
342
if debug.logging:
343
print(f"Bing: Retry: {e}")
344
headers = create_headers()
345
await asyncio.sleep(sleep_retry)
346
continue
347
348
image_request = await upload_image(session, image, tone, headers) if image else None
329
349
async with session.ws_connect(
330
350
'wss://sydney.bing.com/sydney/ChatHub',
331
351
autoping=False,
332
params={'sec_access_token': conversation.conversationSignature}
352
params={'sec_access_token': conversation.conversationSignature},
353
headers=headers
333
354
) as wss:
334
355
await wss.send_str(format_message({'protocol': 'json', 'version': 1}))
335
356
await wss.send_str(format_message({"type": 6}))
@@ -337,11 +358,12 @@ async def stream_generate(
337
358
await wss.send_str(create_message(conversation, prompt, tone, context, image_request, web_search, gpt4_turbo))
338
359
response_txt = ''
339
360
returned_text = ''
340
final = False
341
361
message_id = None
342
while not final:
362
while do_read:
343
363
msg = await wss.receive(timeout=timeout)
344
if not msg.data:
364
if msg.type == WSMsgType.CLOSED:
365
break
366
if msg.type != WSMsgType.TEXT or not msg.data:
345
367
continue
346
368
objects = msg.data.split(Defaults.delimiter)
347
369
for obj in objects:
@@ -350,7 +372,6 @@ async def stream_generate(
350
372
response = json.loads(obj)
351
373
if response and response.get('type') == 1 and response['arguments'][0].get('messages'):
352
374
message = response['arguments'][0]['messages'][0]
353
# Reset memory, if we have a new message
354
375
if message_id is not None and message_id != message["messageId"]:
355
376
returned_text = ''
356
377
message_id = message["messageId"]
@@ -369,7 +390,7 @@ async def stream_generate(
369
390
image_response = ImageResponse(await create_images(session, prompt), prompt, {"preview": "{image}?w=200&h=200"})
370
391
except:
371
392
response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
372
final = True
393
do_read = False
373
394
if response_txt.startswith(returned_text):
374
395
new = response_txt[len(returned_text):]
375
396
if new != "\n":
@@ -380,10 +401,18 @@ async def stream_generate(
380
401
elif response.get('type') == 2:
381
402
result = response['item']['result']
382
403
if result.get('error'):
383
if result["value"] == "CaptchaChallenge":
384
raise Exception(f"{result['value']}: Use other cookies or/and ip address")
385
else:
386
raise Exception(f"{result['value']}: {result['message']}")
404
max_retries -= 1
405
if max_retries < 1:
406
if result["value"] == "CaptchaChallenge":
407
raise RuntimeError(f"{result['value']}: Use other cookies or/and ip address")
408
else:
409
raise RuntimeError(f"{result['value']}: {result['message']}")
410
if debug.logging:
411
print(f"Bing: Retry: {result['value']}: {result['message']}")
412
headers = create_headers()
413
do_read = False
414
conversation = None
415
await asyncio.sleep(sleep_retry)
416
break
387
417
return
388
finally:
389
await delete_conversation(session, conversation)
418
await delete_conversation(session, headers, conversation)
@@ -1,8 +1,6 @@
1
1
from __future__ import annotations
2
2
3
import uuid
4
3
from aiohttp import ClientSession
5
from ...errors import ResponseStatusError
6
4
from ...requests import raise_for_status
7
5
8
6
class Conversation:
@@ -22,7 +20,7 @@ class Conversation:
22
20
self.clientId = clientId
23
21
self.conversationSignature = conversationSignature
24
22
25
async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
23
async def create_conversation(session: ClientSession, headers: dict) -> Conversation:
26
24
"""
27
25
Create a new conversation asynchronously.
28
26
@@ -33,33 +31,15 @@ async def create_conversation(session: ClientSession, proxy: str = None) -> Conv
33
31
Returns:
34
32
Conversation: An instance representing the created conversation.
35
33
"""
36
url = 'https://www.bing.com/search?toncp=0&FORM=hpcodx&q=Bing+AI&showconv=1&cc=en'
37
headers = {
38
"cookie": "; ".join(f"{c.key}={c.value}" for c in session.cookie_jar)
39
}
34
url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1626.1"
40
35
async with session.get(url, headers=headers) as response:
41
await raise_for_status(response)
42
headers = {
43
"accept": "application/json",
44
"sec-fetch-dest": "empty",
45
"sec-fetch-mode": "cors",
46
"sec-fetch-site": "same-origin",
47
"x-ms-client-request-id": str(uuid.uuid4()),
48
"x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.3 OS/Windows",
49
"referer": "https://www.bing.com/search?toncp=0&FORM=hpcodx&q=Bing+AI&showconv=1&cc=en",
50
"cookie": "; ".join(f"{c.key}={c.value}" for c in session.cookie_jar)
51
}
52
url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1634.0-service-contracts"
53
async with session.get(url, headers=headers, proxy=proxy) as response:
54
if response.status == 404:
55
raise ResponseStatusError(f"Response {response.status}: Can't create a new chat")
56
await raise_for_status(response)
36
await raise_for_status(response, "Failed to create conversation")
57
37
data = await response.json()
58
38
conversationId = data.get('conversationId')
59
39
clientId = data.get('clientId')
60
40
conversationSignature = response.headers.get('X-Sydney-Encryptedconversationsignature')
61
41
if not conversationId or not clientId or not conversationSignature:
62
raise Exception('Failed to create conversation.')
42
raise RuntimeError('Empty fields: Failed to create conversation')
63
43
return Conversation(conversationId, clientId, conversationSignature)
64
44
65
45
async def list_conversations(session: ClientSession) -> list:
@@ -76,8 +56,8 @@ async def list_conversations(session: ClientSession) -> list:
76
56
async with session.get(url) as response:
77
57
response = await response.json()
78
58
return response["chats"]
79
80
async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> bool:
59
60
async def delete_conversation(session: ClientSession, conversation: Conversation, headers: dict) -> bool:
81
61
"""
82
62
Delete a conversation asynchronously.
83
63
@@ -98,7 +78,7 @@ async def delete_conversation(session: ClientSession, conversation: Conversation
98
78
"optionsSets": ["autosave"]
99
79
}
100
80
try:
101
async with session.post(url, json=json, proxy=proxy) as response:
81
async with session.post(url, json=json, headers=headers) as response:
102
82
response = await response.json()
103
83
return response["result"]["value"] == "Success"
104
84
except:
@@ -9,6 +9,7 @@ from aiohttp import ClientSession, FormData
9
9
10
10
from ...typing import ImageType, Tuple
11
11
from ...image import to_image, process_image, to_base64_jpg, ImageRequest, Image
12
from ...requests import raise_for_status
12
13
13
14
IMAGE_CONFIG = {
14
15
"maxImagePixels": 360000,
@@ -20,7 +21,7 @@ async def upload_image(
20
21
session: ClientSession,
21
22
image_data: ImageType,
22
23
tone: str,
23
proxy: str = None
24
headers: dict
24
25
) -> ImageRequest:
25
26
"""
26
27
Uploads an image to Bing's AI service and returns the image response.
@@ -43,11 +44,9 @@ async def upload_image(
43
44
img_binary_data = to_base64_jpg(image, IMAGE_CONFIG['imageCompressionRate'])
44
45
45
46
data = build_image_upload_payload(img_binary_data, tone)
46
headers = prepare_headers(session)
47
47
48
async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as response:
49
if response.status != 200:
50
raise RuntimeError("Failed to upload image.")
48
async with session.post("https://www.bing.com/images/kblob", data=data, headers=prepare_headers(headers)) as response:
49
await raise_for_status(response, "Failed to upload image")
51
50
return parse_image_response(await response.json())
52
51
53
52
def calculate_new_dimensions(image: Image) -> Tuple[int, int]:
@@ -109,7 +108,7 @@ def build_knowledge_request(tone: str) -> dict:
109
108
}
110
109
}
111
110
112
def prepare_headers(session: ClientSession) -> dict:
111
def prepare_headers(headers: dict) -> dict:
113
112
"""
114
113
Prepares the headers for the image upload request.
115
114
@@ -120,7 +119,6 @@ def prepare_headers(session: ClientSession) -> dict:
120
119
Returns:
121
120
dict: The headers for the request.
122
121
"""
123
headers = session.headers.copy()
124
122
headers["Referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
125
123
headers["Origin"] = 'https://www.bing.com'
126
124
return headers
@@ -40,7 +40,7 @@ def get_cookies(domain_name: str = '', raise_requirements_error: bool = True, si
40
40
"""
41
41
if domain_name in _cookies:
42
42
return _cookies[domain_name]
43
43
44
44
cookies = load_cookies_from_browsers(domain_name, raise_requirements_error, single_browser)
45
45
_cookies[domain_name] = cookies
46
46
return cookies
@@ -751,8 +751,12 @@ function count_words(text) {
751
751
return text.trim().match(/[\w\u4E00-\u9FA5]+/gu)?.length || 0;
752
752
}
753
753
754
function count_chars(text) {
755
return text.match(/[^\s\p{P}]/gu)?.length || 0;
756
}
757
754
758
function count_words_and_tokens(text, model) {
755
return `(${count_words(text)} words, ${count_tokens(model, text)} tokens)`;
759
return `(${count_words(text)} words, ${count_chars(text)} chars, ${count_tokens(model, text)} tokens)`;
756
760
}
757
761
758
762
let countFocus = messageInput;
@@ -1,7 +1,6 @@
1
1
from __future__ import annotations
2
2
3
3
import random
4
import secrets
5
4
import string
6
5
7
6
from ..typing import Messages
@@ -40,11 +39,14 @@ def get_random_string(length: int = 10) -> str:
40
39
for _ in range(length)
41
40
)
42
41
43
def get_random_hex() -> str:
42
def get_random_hex(length: int = 32) -> str:
44
43
"""
45
Generate a random hexadecimal string of a fixed length.
44
Generate a random hexadecimal string with n length.
46
45
47
46
Returns:
48
str: A random hexadecimal string of 32 characters (16 bytes).
47
str: A random hexadecimal string of n characters.
49
48
"""
50
return secrets.token_hex(16).zfill(32)
49
return ''.join(
50
random.choice("abcdef" + string.digits)
51
for _ in range(length)
52
)
@@ -1,6 +1,5 @@
1
1
from __future__ import annotations
2
2
3
from urllib.parse import urlparse
4
3
from typing import Union
5
4
from aiohttp import ClientResponse
6
5
from requests import Response as RequestsResponse
@@ -15,7 +14,7 @@ except ImportError:
15
14
has_curl_cffi = False
16
15
17
16
from ..webdriver import WebDriver, WebDriverSession
18
from ..webdriver import user_config_dir, bypass_cloudflare, get_driver_cookies
17
from ..webdriver import bypass_cloudflare, get_driver_cookies
19
18
from ..errors import MissingRequirementsError, RateLimitError, ResponseStatusError
20
19
from .defaults import DEFAULT_HEADERS
21
20
@@ -39,17 +38,16 @@ def get_args_from_browser(
39
38
Returns:
40
39
Session: A Session object configured with cookies and headers from the WebDriver.
41
40
"""
42
user_data_dir = "" #user_config_dir(f"g4f-{urlparse(url).hostname}")
43
with WebDriverSession(webdriver, user_data_dir, proxy=proxy, virtual_display=virtual_display) as driver:
41
with WebDriverSession(webdriver, "", proxy=proxy, virtual_display=virtual_display) as driver:
44
42
if do_bypass_cloudflare:
45
43
bypass_cloudflare(driver, url, timeout)
46
user_agent = driver.execute_script("return navigator.userAgent")
47
44
headers = {
48
45
**DEFAULT_HEADERS,
49
46
'referer': url,
50
'user-agent': user_agent,
51
47
}
52
if hasattr(driver, "requests"):
48
if not hasattr(driver, "requests"):
49
headers["user-agent"] = driver.execute_script("return navigator.userAgent")
50
else:
53
51
for request in driver.requests:
54
52
if request.url.startswith(url):
55
53
for key, value in request.headers.items():
@@ -83,22 +81,22 @@ def get_session_from_browser(url: str, webdriver: WebDriver = None, proxy: str =
83
81
impersonate="chrome"
84
82
)
85
83
86
async def raise_for_status_async(response: Union[StreamResponse, ClientResponse]):
84
async def raise_for_status_async(response: Union[StreamResponse, ClientResponse], message: str = None):
87
85
if response.status in (429, 402):
88
86
raise RateLimitError(f"Response {response.status}: Rate limit reached")
89
text = await response.text() if not response.ok else None
90
if response.status == 403 and "<title>Just a moment...</title>" in text:
87
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
89
raise ResponseStatusError(f"Response {response.status}: Cloudflare detected")
92
90
elif not response.ok:
93
raise ResponseStatusError(f"Response {response.status}: {text}")
91
raise ResponseStatusError(f"Response {response.status}: {message}")
94
92
95
def raise_for_status(response: Union[StreamResponse, ClientResponse, Response, RequestsResponse]):
93
def raise_for_status(response: Union[StreamResponse, ClientResponse, Response, RequestsResponse], message: str = None):
96
94
if isinstance(response, StreamSession) or isinstance(response, ClientResponse):
97
return raise_for_status_async(response)
95
return raise_for_status_async(response, message)
98
96
99
97
if response.status_code in (429, 402):
100
98
raise RateLimitError(f"Response {response.status_code}: Rate limit reached")
101
99
elif response.status_code == 403 and "<title>Just a moment...</title>" in response.text:
102
100
raise ResponseStatusError(f"Response {response.status_code}: Cloudflare detected")
103
101
elif not response.ok:
104
raise ResponseStatusError(f"Response {response.status_code}: {response.text}")
102
raise ResponseStatusError(f"Response {response.status_code}: {response.text if message is None else message}")
@@ -10,10 +10,9 @@ try:
10
10
from selenium.webdriver.support import expected_conditions as EC
11
11
from selenium.webdriver.common.keys import Keys
12
12
from selenium.common.exceptions import NoSuchElementException
13
from undetected_chromedriver import Chrome, ChromeOptions
14
13
has_requirements = True
15
14
except ImportError:
16
from typing import Type as WebDriver, Callable as user_config_dir
15
from typing import Type as WebDriver
17
16
has_requirements = False
18
17
19
18
import time
@@ -38,9 +37,9 @@ try:
38
37
def __init__(self, *args, options=None, seleniumwire_options={}, **kwargs):
39
38
if options is None:
40
39
options = ChromeOptions()
41
options.add_argument('--proxy-bypass-list=<-loopback>')
42
40
config = self._setup_backend(seleniumwire_options)
43
41
options.add_argument(f"--proxy-server={config['proxy']['httpProxy']}")
42
options.add_argument('--proxy-bypass-list=<-loopback>')
44
43
options.add_argument("--ignore-certificate-errors")
45
44
super().__init__(*args, options=options, **kwargs)
46
45
has_seleniumwire = True