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

XFEstudio/gpt4free

Add count chars to gui, Add retry support to fix rate limit in Bing

5bcf21f9
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

8 个文件 +123 -113
Modified g4f/Provider/Bing.py +84 -55
@@ -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)
Modified g4f/Provider/bing/conversation.py +7 -27
@@ -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:
Modified g4f/Provider/bing/upload_image.py +5 -7
@@ -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
Modified g4f/cookies.py +1 -1
@@ -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
Modified g4f/gui/client/js/chat.v1.js +5 -1
@@ -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;
Modified g4f/providers/helper.py +7 -5
@@ -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 )
Modified g4f/requests/__init__.py +12 -14
@@ -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}")
Modified g4f/webdriver.py +2 -3
@@ -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