summaryrefslogtreecommitdiffstats
path: root/g4f/Provider/Bing.py
diff options
context:
space:
mode:
Diffstat (limited to 'g4f/Provider/Bing.py')
-rw-r--r--g4f/Provider/Bing.py315
1 files changed, 67 insertions, 248 deletions
diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py
index e03b413f..b2e10e5d 100644
--- a/g4f/Provider/Bing.py
+++ b/g4f/Provider/Bing.py
@@ -1,35 +1,25 @@
from __future__ import annotations
-import string
import random
import json
import os
-import re
-import io
-import base64
-import numpy as np
import uuid
-import urllib.parse
import time
-from PIL import Image
-from aiohttp import ClientSession, ClientTimeout
-from ..typing import AsyncResult, Messages
+from urllib import parse
+from aiohttp import ClientSession, ClientTimeout
+
+from ..typing import AsyncResult, Messages
from .base_provider import AsyncGeneratorProvider
+from ..webdriver import get_browser, get_driver_cookies
+from .bing.upload_image import upload_image
+from .bing.create_images import create_images, format_images_markdown, wait_for_login
+from .bing.conversation import Conversation, create_conversation, delete_conversation
class Tones():
creative = "Creative"
balanced = "Balanced"
precise = "Precise"
-default_cookies = {
- 'SRCHD' : 'AF=NOFORM',
- 'PPLState' : '1',
- 'KievRPSSecAuth': '',
- 'SUID' : '',
- 'SRCHUSR' : '',
- 'SRCHHPGUSR' : f'HV={int(time.time())}',
-}
-
class Bing(AsyncGeneratorProvider):
url = "https://bing.com/chat"
working = True
@@ -55,9 +45,9 @@ class Bing(AsyncGeneratorProvider):
context = create_context(messages[:-1])
if not cookies:
- cookies = default_cookies
+ cookies = Defaults.cookies
else:
- for key, value in default_cookies.items():
+ for key, value in Defaults.cookies.items():
if key not in cookies:
cookies[key] = value
@@ -71,106 +61,6 @@ def create_context(messages: Messages):
for message in messages
)
-class Conversation():
- def __init__(self, conversationId: str, clientId: str, conversationSignature: str, imageInfo: dict=None) -> None:
- self.conversationId = conversationId
- self.clientId = clientId
- self.conversationSignature = conversationSignature
- self.imageInfo = imageInfo
-
-async def create_conversation(session: ClientSession, tone: str, image: str = None, proxy: str = None) -> Conversation:
- url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
- async with session.get(url, proxy=proxy) as response:
- data = await response.json()
-
- conversationId = data.get('conversationId')
- clientId = data.get('clientId')
- conversationSignature = response.headers.get('X-Sydney-Encryptedconversationsignature')
-
- if not conversationId or not clientId or not conversationSignature:
- raise Exception('Failed to create conversation.')
- conversation = Conversation(conversationId, clientId, conversationSignature, None)
- if isinstance(image,str):
- try:
- config = {
- "visualSearch": {
- "maxImagePixels": 360000,
- "imageCompressionRate": 0.7,
- "enableFaceBlurDebug": 0,
- }
- }
- is_data_uri_an_image(image)
- img_binary_data = extract_data_uri(image)
- is_accepted_format(img_binary_data)
- img = Image.open(io.BytesIO(img_binary_data))
- width, height = img.size
- max_image_pixels = config['visualSearch']['maxImagePixels']
- compression_rate = config['visualSearch']['imageCompressionRate']
-
- if max_image_pixels / (width * height) < 1:
- new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
- new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
- else:
- new_width = width
- new_height = height
- try:
- orientation = get_orientation(img)
- except Exception:
- orientation = None
- new_img = process_image(orientation, img, new_width, new_height)
- new_img_binary_data = compress_image_to_base64(new_img, compression_rate)
- data, boundary = build_image_upload_api_payload(new_img_binary_data, conversation, tone)
- headers = session.headers.copy()
- headers["content-type"] = f'multipart/form-data; boundary={boundary}'
- headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
- headers["origin"] = 'https://www.bing.com'
- async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as image_upload_response:
- if image_upload_response.status != 200:
- raise Exception("Failed to upload image.")
-
- image_info = await image_upload_response.json()
- if not image_info.get('blobId'):
- raise Exception("Failed to parse image info.")
- result = {'bcid': image_info.get('blobId', "")}
- result['blurredBcid'] = image_info.get('processedBlobId', "")
- if result['blurredBcid'] != "":
- result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
- elif result['bcid'] != "":
- result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
- result['originalImageUrl'] = (
- "https://www.bing.com/images/blob?bcid="
- + result['blurredBcid']
- if config['visualSearch']["enableFaceBlurDebug"]
- else "https://www.bing.com/images/blob?bcid="
- + result['bcid']
- )
- conversation.imageInfo = result
- except Exception as e:
- print(f"An error happened while trying to send image: {str(e)}")
- return conversation
-
-async def list_conversations(session: ClientSession) -> list:
- url = "https://www.bing.com/turing/conversation/chats"
- async with session.get(url) as response:
- response = await response.json()
- return response["chats"]
-
-async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> list:
- url = "https://sydney.bing.com/sydney/DeleteSingleConversation"
- json = {
- "conversationId": conversation.conversationId,
- "conversationSignature": conversation.conversationSignature,
- "participant": {"id": conversation.clientId},
- "source": "cib",
- "optionsSets": ["autosave"]
- }
- async with session.post(url, json=json, proxy=proxy) as response:
- try:
- response = await response.json()
- return response["result"]["value"] == "Success"
- except:
- return False
-
class Defaults:
delimiter = "\x1e"
ip_address = f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
@@ -264,123 +154,28 @@ class Defaults:
'eredirecturl',
'nojbfedge'
]
+
+ cookies = {
+ 'SRCHD' : 'AF=NOFORM',
+ 'PPLState' : '1',
+ 'KievRPSSecAuth': '',
+ 'SUID' : '',
+ 'SRCHUSR' : '',
+ 'SRCHHPGUSR' : f'HV={int(time.time())}',
+ }
def format_message(msg: dict) -> str:
return json.dumps(msg, ensure_ascii=False) + Defaults.delimiter
-def build_image_upload_api_payload(image_bin: str, conversation: Conversation, tone: str):
- payload = {
- 'invokedSkills': ["ImageById"],
- 'subscriptionId': "Bing.Chat.Multimodal",
- 'invokedSkillsRequestData': {
- 'enableFaceBlur': True
- },
- 'convoData': {
- 'convoid': "",
- 'convotone': tone
- }
- }
- knowledge_request = {
- 'imageInfo': {},
- 'knowledgeRequest': payload
- }
- boundary="----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
- data = (
- f'--{boundary}'
- + '\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n'
- + json.dumps(knowledge_request, ensure_ascii=False)
- + "\r\n--"
- + boundary
- + '\r\nContent-Disposition: form-data; name="imageBase64"\r\n\r\n'
- + image_bin
- + "\r\n--"
- + boundary
- + "--\r\n"
- )
- return data, boundary
-
-def is_data_uri_an_image(data_uri: str):
- try:
- # Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
- if not re.match(r'data:image/(\w+);base64,', data_uri):
- raise ValueError("Invalid data URI image.")
- # Extract the image format from the data URI
- image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
- # Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
- if image_format.lower() not in ['jpeg', 'jpg', 'png', 'gif']:
- raise ValueError("Invalid image format (from mime file type).")
- except Exception as e:
- raise e
-
-def is_accepted_format(binary_data: bytes) -> bool:
- try:
- check = False
- if binary_data.startswith(b'\xFF\xD8\xFF'):
- check = True # It's a JPEG image
- elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
- check = True # It's a PNG image
- elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
- check = True # It's a GIF image
- elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
- check = True # It's a JPEG image
- elif binary_data.startswith(b'\xFF\xD8'):
- check = True # It's a JPEG image
- elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
- check = True # It's a WebP image
- # else we raise ValueError
- if not check:
- raise ValueError("Invalid image format (from magic code).")
- except Exception as e:
- raise e
-
-def extract_data_uri(data_uri: str) -> bytes:
- try:
- data = data_uri.split(",")[1]
- data = base64.b64decode(data)
- return data
- except Exception as e:
- raise e
-
-def get_orientation(data: bytes) -> int:
- try:
- if data[:2] != b'\xFF\xD8':
- raise Exception('NotJpeg')
- with Image.open(data) as img:
- exif_data = img._getexif()
- if exif_data is not None:
- orientation = exif_data.get(274) # 274 corresponds to the orientation tag in EXIF
- if orientation is not None:
- return orientation
- except Exception:
- pass
-
-def process_image(orientation: int, img: Image.Image, new_width: int, new_height: int) -> Image.Image:
- try:
- # Initialize the canvas
- new_img = Image.new("RGB", (new_width, new_height), color="#FFFFFF")
- if orientation:
- if orientation > 4:
- img = img.transpose(Image.FLIP_LEFT_RIGHT)
- if orientation in [3, 4]:
- img = img.transpose(Image.ROTATE_180)
- if orientation in [5, 6]:
- img = img.transpose(Image.ROTATE_270)
- if orientation in [7, 8]:
- img = img.transpose(Image.ROTATE_90)
- new_img.paste(img, (0, 0))
- return new_img
- except Exception as e:
- raise e
-
-def compress_image_to_base64(img, compression_rate) -> str:
- try:
- output_buffer = io.BytesIO()
- img.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
- return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
- except Exception as e:
- raise e
-
-def create_message(conversation: Conversation, prompt: str, tone: str, context: str = None, web_search: bool = False, gpt4_turbo: bool = False) -> str:
+def create_message(
+ conversation: Conversation,
+ prompt: str,
+ tone: str,
+ context: str = None,
+ image_info: dict = None,
+ web_search: bool = False,
+ gpt4_turbo: bool = False
+) -> str:
options_sets = Defaults.optionsSets
if tone == Tones.creative:
options_sets.append("h3imaginative")
@@ -429,9 +224,9 @@ def create_message(conversation: Conversation, prompt: str, tone: str, context:
'target': 'chat',
'type': 4
}
- if conversation.imageInfo != None and "imageUrl" in conversation.imageInfo and "originalImageUrl" in conversation.imageInfo:
- struct['arguments'][0]['message']['originalImageUrl'] = conversation.imageInfo['originalImageUrl']
- struct['arguments'][0]['message']['imageUrl'] = conversation.imageInfo['imageUrl']
+ if image_info and "imageUrl" in image_info and "originalImageUrl" in image_info:
+ struct['arguments'][0]['message']['originalImageUrl'] = image_info['originalImageUrl']
+ struct['arguments'][0]['message']['imageUrl'] = image_info['imageUrl']
struct['arguments'][0]['experienceType'] = None
struct['arguments'][0]['attachedFileInfo'] = {"fileName": None, "fileType": None}
if context:
@@ -454,28 +249,39 @@ async def stream_generate(
web_search: bool = False,
gpt4_turbo: bool = False
):
+ headers = Defaults.headers
+ if cookies:
+ headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
async with ClientSession(
- timeout=ClientTimeout(total=900),
- headers=Defaults.headers if not cookies else {**Defaults.headers, "Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items())},
- ) as session:
- conversation = await create_conversation(session, tone, image, proxy)
+ timeout=ClientTimeout(total=900),
+ headers=headers
+ ) as session:
+ conversation = await create_conversation(session, proxy)
+ image_info = None
+ if image:
+ image_info = await upload_image(session, image, tone, proxy)
try:
- async with session.ws_connect('wss://sydney.bing.com/sydney/ChatHub', autoping=False, params={'sec_access_token': conversation.conversationSignature}, proxy=proxy) as wss:
-
+ async with session.ws_connect(
+ 'wss://sydney.bing.com/sydney/ChatHub',
+ autoping=False,
+ params={'sec_access_token': conversation.conversationSignature},
+ proxy=proxy
+ ) as wss:
await wss.send_str(format_message({'protocol': 'json', 'version': 1}))
await wss.receive(timeout=900)
- await wss.send_str(create_message(conversation, prompt, tone, context, web_search, gpt4_turbo))
+ await wss.send_str(create_message(conversation, prompt, tone, context, image_info, web_search, gpt4_turbo))
response_txt = ''
returned_text = ''
final = False
while not final:
msg = await wss.receive(timeout=900)
+ if not msg.data:
+ continue
objects = msg.data.split(Defaults.delimiter)
for obj in objects:
if obj is None or not obj:
continue
-
response = json.loads(obj)
if response.get('type') == 1 and response['arguments'][0].get('messages'):
message = response['arguments'][0]['messages'][0]
@@ -488,9 +294,11 @@ async def stream_generate(
inline_txt = card['inlines'][0].get('text')
response_txt += inline_txt + '\n'
elif message.get('contentType') == "IMAGE":
- query = urllib.parse.quote(message.get('text'))
- url = f"\nhttps://www.bing.com/images/create?q={query}"
- response_txt += url
+ prompt = message.get('text')
+ try:
+ response_txt += format_images_markdown(await create_images(session, prompt, proxy), prompt)
+ except:
+ response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
final = True
if response_txt.startswith(returned_text):
new = response_txt[len(returned_text):]
@@ -500,7 +308,18 @@ async def stream_generate(
elif response.get('type') == 2:
result = response['item']['result']
if result.get('error'):
- raise Exception(f"{result['value']}: {result['message']}")
+ if result["value"] == "CaptchaChallenge":
+ driver = get_browser(proxy=proxy)
+ try:
+ for chunk in wait_for_login(driver):
+ yield chunk
+ cookies = get_driver_cookies(driver)
+ finally:
+ driver.quit()
+ async for chunk in stream_generate(prompt, tone, image, context, proxy, cookies, web_search, gpt4_turbo):
+ yield chunk
+ else:
+ raise Exception(f"{result['value']}: {result['message']}")
return
finally:
await delete_conversation(session, conversation, proxy)