返回提交历史
Modified
g4f/Provider/Bestim.py
+17
-26
Modified
g4f/Provider/Bing.py
+67
-248
Added
g4f/Provider/FreeChatgpt.py
+68
-0
Modified
g4f/Provider/__init__.py
+2
-0
Added
g4f/Provider/bing/conversation.py
+43
-0
Added
g4f/Provider/bing/create_images.py
+164
-0
Added
g4f/Provider/bing/upload_image.py
+162
-0
Added
g4f/Provider/create_images.py
+84
-0
Modified
g4f/Provider/needs_auth/Bard.py
+8
-5
Modified
g4f/__init__.py
+8
-0
Modified
g4f/gui/client/html/index.html
+5
-0
Modified
g4f/gui/client/js/chat.v1.js
+20
-6
Modified
g4f/gui/server/backend.py
+14
-12
Modified
g4f/gui/server/internet.py
+1
-1
Modified
g4f/models.py
+2
-5
Modified
g4f/requests.py
+2
-3
Modified
g4f/version.py
+1
-1
Modified
g4f/webdriver.py
+3
-0
Modified
requirements.txt
+2
-1
XFEstudio/gpt4free
Add create images to Bing (#1426)
Add create images from Bing Add FreeChatgpt Provider Fix Bard Provider
bee75be8
代码差异
19 个文件
+673
-308
@@ -1,42 +1,35 @@
1
1
from __future__ import annotations
2
2
3
from ..typing import Messages, List, Dict
3
from ..typing import Messages
4
4
from .base_provider import BaseProvider, CreateResult
5
from ..requests import get_session_from_browser
5
6
from uuid import uuid4
6
7
import requests
7
8
8
def format_prompt(messages) -> List[Dict[str, str]]:
9
10
return [{"id": str(uuid4()), "content": '\n'.join(f'{m["role"]}: {m["content"]}' for m in messages), "from": "you"}]
11
12
9
class Bestim(BaseProvider):
13
10
url = "https://chatgpt.bestim.org"
14
11
supports_gpt_35_turbo = True
15
12
supports_message_history = True
16
working = True
13
working = False
17
14
supports_stream = True
18
15
19
@staticmethod
16
@classmethod
20
17
def create_completion(
18
cls,
21
19
model: str,
22
20
messages: Messages,
23
21
stream: bool,
24
22
proxy: str = None,
25
23
**kwargs
26
24
) -> CreateResult:
27
25
session = get_session_from_browser(cls.url, proxy=proxy)
28
26
headers = {
29
'POST': '/chat/send2/ HTTP/3',
30
'Host': 'chatgpt.bestim.org',
31
27
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
32
28
'Accept': 'application/json, text/event-stream',
33
29
'Accept-Language': 'en-US,en;q=0.5',
34
30
'Accept-Encoding': 'gzip, deflate, br',
35
31
'Referer': 'https://chatgpt.bestim.org/chat/',
36
'Content-Type': 'application/json',
37
'Content-Length': '109',
38
32
'Origin': 'https://chatgpt.bestim.org',
39
'Cookie': 'NpZAER=qKkRHguMIOraVbJAWpoyzGLFjZwYlm; qKkRHguMIOraVbJAWpoyzGLFjZwYlm=8ebb5ae1561bde05354de5979b52c6e1-1704058188-1704058188; NpZAER_hits=2; _csrf-front=fcf20965823c0a152ae8f9cdf15b23022bb26cdc6bf32a9d4c8bfe78dcc6b807a%3A2%3A%7Bi%3A0%3Bs%3A11%3A%22_csrf-front%22%3Bi%3A1%3Bs%3A32%3A%22a5wP6azsc7dxV8rmwAXaNsl8XS1yvW5V%22%3B%7D',
40
33
'Alt-Used': 'chatgpt.bestim.org',
41
34
'Connection': 'keep-alive',
42
35
'Sec-Fetch-Dest': 'empty',
@@ -44,27 +37,25 @@ class Bestim(BaseProvider):
44
37
'Sec-Fetch-Site': 'same-origin',
45
38
'TE': 'trailers'
46
39
}
47
48
40
data = {
49
50
"messagesHistory": format_prompt(messages),
41
"messagesHistory": [{
42
"id": str(uuid4()),
43
"content": m["content"],
44
"from": "you" if m["role"] == "user" else "bot"
45
} for m in messages],
51
46
"type": "chat",
52
47
}
53
54
response = requests.post(
48
response = session.post(
55
49
url="https://chatgpt.bestim.org/chat/send2/",
56
50
headers=headers,
57
51
json=data,
58
proxies={"https": proxy}
52
proxies={"https": proxy},
53
stream=True
59
54
)
60
61
55
response.raise_for_status()
62
63
for chunk in response.iter_lines():
64
65
if b"event: trylimit" not in chunk:
66
67
yield chunk.decode().removeprefix("data: ")
56
for line in response.iter_lines():
57
if not line.startswith(b"event: trylimit"):
58
yield line.decode().removeprefix("data: ")
68
59
69
60
70
61
@@ -1,35 +1,25 @@
1
1
from __future__ import annotations
2
2
3
import string
4
3
import random
5
4
import json
6
5
import os
7
import re
8
import io
9
import base64
10
import numpy as np
11
6
import uuid
12
import urllib.parse
13
7
import time
14
from PIL import Image
15
from aiohttp import ClientSession, ClientTimeout
16
from ..typing import AsyncResult, Messages
8
from urllib import parse
9
from aiohttp import ClientSession, ClientTimeout
10
11
from ..typing import AsyncResult, Messages
17
12
from .base_provider import AsyncGeneratorProvider
13
from ..webdriver import get_browser, get_driver_cookies
14
from .bing.upload_image import upload_image
15
from .bing.create_images import create_images, format_images_markdown, wait_for_login
16
from .bing.conversation import Conversation, create_conversation, delete_conversation
18
17
19
18
class Tones():
20
19
creative = "Creative"
21
20
balanced = "Balanced"
22
21
precise = "Precise"
23
22
24
default_cookies = {
25
'SRCHD' : 'AF=NOFORM',
26
'PPLState' : '1',
27
'KievRPSSecAuth': '',
28
'SUID' : '',
29
'SRCHUSR' : '',
30
'SRCHHPGUSR' : f'HV={int(time.time())}',
31
}
32
33
23
class Bing(AsyncGeneratorProvider):
34
24
url = "https://bing.com/chat"
35
25
working = True
@@ -55,9 +45,9 @@ class Bing(AsyncGeneratorProvider):
55
45
context = create_context(messages[:-1])
56
46
57
47
if not cookies:
58
cookies = default_cookies
48
cookies = Defaults.cookies
59
49
else:
60
for key, value in default_cookies.items():
50
for key, value in Defaults.cookies.items():
61
51
if key not in cookies:
62
52
cookies[key] = value
63
53
@@ -71,106 +61,6 @@ def create_context(messages: Messages):
71
61
for message in messages
72
62
)
73
63
74
class Conversation():
75
def __init__(self, conversationId: str, clientId: str, conversationSignature: str, imageInfo: dict=None) -> None:
76
self.conversationId = conversationId
77
self.clientId = clientId
78
self.conversationSignature = conversationSignature
79
self.imageInfo = imageInfo
80
81
async def create_conversation(session: ClientSession, tone: str, image: str = None, proxy: str = None) -> Conversation:
82
url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
83
async with session.get(url, proxy=proxy) as response:
84
data = await response.json()
85
86
conversationId = data.get('conversationId')
87
clientId = data.get('clientId')
88
conversationSignature = response.headers.get('X-Sydney-Encryptedconversationsignature')
89
90
if not conversationId or not clientId or not conversationSignature:
91
raise Exception('Failed to create conversation.')
92
conversation = Conversation(conversationId, clientId, conversationSignature, None)
93
if isinstance(image,str):
94
try:
95
config = {
96
"visualSearch": {
97
"maxImagePixels": 360000,
98
"imageCompressionRate": 0.7,
99
"enableFaceBlurDebug": 0,
100
}
101
}
102
is_data_uri_an_image(image)
103
img_binary_data = extract_data_uri(image)
104
is_accepted_format(img_binary_data)
105
img = Image.open(io.BytesIO(img_binary_data))
106
width, height = img.size
107
max_image_pixels = config['visualSearch']['maxImagePixels']
108
compression_rate = config['visualSearch']['imageCompressionRate']
109
110
if max_image_pixels / (width * height) < 1:
111
new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
112
new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
113
else:
114
new_width = width
115
new_height = height
116
try:
117
orientation = get_orientation(img)
118
except Exception:
119
orientation = None
120
new_img = process_image(orientation, img, new_width, new_height)
121
new_img_binary_data = compress_image_to_base64(new_img, compression_rate)
122
data, boundary = build_image_upload_api_payload(new_img_binary_data, conversation, tone)
123
headers = session.headers.copy()
124
headers["content-type"] = f'multipart/form-data; boundary={boundary}'
125
headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
126
headers["origin"] = 'https://www.bing.com'
127
async with session.post("https://www.bing.com/images/kblob", data=data, headers=headers, proxy=proxy) as image_upload_response:
128
if image_upload_response.status != 200:
129
raise Exception("Failed to upload image.")
130
131
image_info = await image_upload_response.json()
132
if not image_info.get('blobId'):
133
raise Exception("Failed to parse image info.")
134
result = {'bcid': image_info.get('blobId', "")}
135
result['blurredBcid'] = image_info.get('processedBlobId', "")
136
if result['blurredBcid'] != "":
137
result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
138
elif result['bcid'] != "":
139
result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
140
result['originalImageUrl'] = (
141
"https://www.bing.com/images/blob?bcid="
142
+ result['blurredBcid']
143
if config['visualSearch']["enableFaceBlurDebug"]
144
else "https://www.bing.com/images/blob?bcid="
145
+ result['bcid']
146
)
147
conversation.imageInfo = result
148
except Exception as e:
149
print(f"An error happened while trying to send image: {str(e)}")
150
return conversation
151
152
async def list_conversations(session: ClientSession) -> list:
153
url = "https://www.bing.com/turing/conversation/chats"
154
async with session.get(url) as response:
155
response = await response.json()
156
return response["chats"]
157
158
async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> list:
159
url = "https://sydney.bing.com/sydney/DeleteSingleConversation"
160
json = {
161
"conversationId": conversation.conversationId,
162
"conversationSignature": conversation.conversationSignature,
163
"participant": {"id": conversation.clientId},
164
"source": "cib",
165
"optionsSets": ["autosave"]
166
}
167
async with session.post(url, json=json, proxy=proxy) as response:
168
try:
169
response = await response.json()
170
return response["result"]["value"] == "Success"
171
except:
172
return False
173
174
64
class Defaults:
175
65
delimiter = "\x1e"
176
66
ip_address = f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
@@ -264,123 +154,28 @@ class Defaults:
264
154
'eredirecturl',
265
155
'nojbfedge'
266
156
]
157
158
cookies = {
159
'SRCHD' : 'AF=NOFORM',
160
'PPLState' : '1',
161
'KievRPSSecAuth': '',
162
'SUID' : '',
163
'SRCHUSR' : '',
164
'SRCHHPGUSR' : f'HV={int(time.time())}',
165
}
267
166
268
167
def format_message(msg: dict) -> str:
269
168
return json.dumps(msg, ensure_ascii=False) + Defaults.delimiter
270
169
271
def build_image_upload_api_payload(image_bin: str, conversation: Conversation, tone: str):
272
payload = {
273
'invokedSkills': ["ImageById"],
274
'subscriptionId': "Bing.Chat.Multimodal",
275
'invokedSkillsRequestData': {
276
'enableFaceBlur': True
277
},
278
'convoData': {
279
'convoid': "",
280
'convotone': tone
281
}
282
}
283
knowledge_request = {
284
'imageInfo': {},
285
'knowledgeRequest': payload
286
}
287
boundary="----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
288
data = (
289
f'--{boundary}'
290
+ '\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n'
291
+ json.dumps(knowledge_request, ensure_ascii=False)
292
+ "\r\n--"
293
+ boundary
294
+ '\r\nContent-Disposition: form-data; name="imageBase64"\r\n\r\n'
295
+ image_bin
296
+ "\r\n--"
297
+ boundary
298
+ "--\r\n"
299
)
300
return data, boundary
301
302
def is_data_uri_an_image(data_uri: str):
303
try:
304
# Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
305
if not re.match(r'data:image/(\w+);base64,', data_uri):
306
raise ValueError("Invalid data URI image.")
307
# Extract the image format from the data URI
308
image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
309
# Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
310
if image_format.lower() not in ['jpeg', 'jpg', 'png', 'gif']:
311
raise ValueError("Invalid image format (from mime file type).")
312
except Exception as e:
313
raise e
314
315
def is_accepted_format(binary_data: bytes) -> bool:
316
try:
317
check = False
318
if binary_data.startswith(b'\xFF\xD8\xFF'):
319
check = True # It's a JPEG image
320
elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
321
check = True # It's a PNG image
322
elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
323
check = True # It's a GIF image
324
elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
325
check = True # It's a JPEG image
326
elif binary_data.startswith(b'\xFF\xD8'):
327
check = True # It's a JPEG image
328
elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
329
check = True # It's a WebP image
330
# else we raise ValueError
331
if not check:
332
raise ValueError("Invalid image format (from magic code).")
333
except Exception as e:
334
raise e
335
336
def extract_data_uri(data_uri: str) -> bytes:
337
try:
338
data = data_uri.split(",")[1]
339
data = base64.b64decode(data)
340
return data
341
except Exception as e:
342
raise e
343
344
def get_orientation(data: bytes) -> int:
345
try:
346
if data[:2] != b'\xFF\xD8':
347
raise Exception('NotJpeg')
348
with Image.open(data) as img:
349
exif_data = img._getexif()
350
if exif_data is not None:
351
orientation = exif_data.get(274) # 274 corresponds to the orientation tag in EXIF
352
if orientation is not None:
353
return orientation
354
except Exception:
355
pass
356
357
def process_image(orientation: int, img: Image.Image, new_width: int, new_height: int) -> Image.Image:
358
try:
359
# Initialize the canvas
360
new_img = Image.new("RGB", (new_width, new_height), color="#FFFFFF")
361
if orientation:
362
if orientation > 4:
363
img = img.transpose(Image.FLIP_LEFT_RIGHT)
364
if orientation in [3, 4]:
365
img = img.transpose(Image.ROTATE_180)
366
if orientation in [5, 6]:
367
img = img.transpose(Image.ROTATE_270)
368
if orientation in [7, 8]:
369
img = img.transpose(Image.ROTATE_90)
370
new_img.paste(img, (0, 0))
371
return new_img
372
except Exception as e:
373
raise e
374
375
def compress_image_to_base64(img, compression_rate) -> str:
376
try:
377
output_buffer = io.BytesIO()
378
img.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
379
return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
380
except Exception as e:
381
raise e
382
383
def create_message(conversation: Conversation, prompt: str, tone: str, context: str = None, web_search: bool = False, gpt4_turbo: bool = False) -> str:
170
def create_message(
171
conversation: Conversation,
172
prompt: str,
173
tone: str,
174
context: str = None,
175
image_info: dict = None,
176
web_search: bool = False,
177
gpt4_turbo: bool = False
178
) -> str:
384
179
options_sets = Defaults.optionsSets
385
180
if tone == Tones.creative:
386
181
options_sets.append("h3imaginative")
@@ -429,9 +224,9 @@ def create_message(conversation: Conversation, prompt: str, tone: str, context:
429
224
'target': 'chat',
430
225
'type': 4
431
226
}
432
if conversation.imageInfo != None and "imageUrl" in conversation.imageInfo and "originalImageUrl" in conversation.imageInfo:
433
struct['arguments'][0]['message']['originalImageUrl'] = conversation.imageInfo['originalImageUrl']
434
struct['arguments'][0]['message']['imageUrl'] = conversation.imageInfo['imageUrl']
227
if image_info and "imageUrl" in image_info and "originalImageUrl" in image_info:
228
struct['arguments'][0]['message']['originalImageUrl'] = image_info['originalImageUrl']
229
struct['arguments'][0]['message']['imageUrl'] = image_info['imageUrl']
435
230
struct['arguments'][0]['experienceType'] = None
436
231
struct['arguments'][0]['attachedFileInfo'] = {"fileName": None, "fileType": None}
437
232
if context:
@@ -454,28 +249,39 @@ async def stream_generate(
454
249
web_search: bool = False,
455
250
gpt4_turbo: bool = False
456
251
):
252
headers = Defaults.headers
253
if cookies:
254
headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
457
255
async with ClientSession(
458
timeout=ClientTimeout(total=900),
459
headers=Defaults.headers if not cookies else {**Defaults.headers, "Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items())},
460
) as session:
461
conversation = await create_conversation(session, tone, image, proxy)
256
timeout=ClientTimeout(total=900),
257
headers=headers
258
) as session:
259
conversation = await create_conversation(session, proxy)
260
image_info = None
261
if image:
262
image_info = await upload_image(session, image, tone, proxy)
462
263
try:
463
async with session.ws_connect('wss://sydney.bing.com/sydney/ChatHub', autoping=False, params={'sec_access_token': conversation.conversationSignature}, proxy=proxy) as wss:
464
264
async with session.ws_connect(
265
'wss://sydney.bing.com/sydney/ChatHub',
266
autoping=False,
267
params={'sec_access_token': conversation.conversationSignature},
268
proxy=proxy
269
) as wss:
465
270
await wss.send_str(format_message({'protocol': 'json', 'version': 1}))
466
271
await wss.receive(timeout=900)
467
await wss.send_str(create_message(conversation, prompt, tone, context, web_search, gpt4_turbo))
272
await wss.send_str(create_message(conversation, prompt, tone, context, image_info, web_search, gpt4_turbo))
468
273
469
274
response_txt = ''
470
275
returned_text = ''
471
276
final = False
472
277
while not final:
473
278
msg = await wss.receive(timeout=900)
279
if not msg.data:
280
continue
474
281
objects = msg.data.split(Defaults.delimiter)
475
282
for obj in objects:
476
283
if obj is None or not obj:
477
284
continue
478
479
285
response = json.loads(obj)
480
286
if response.get('type') == 1 and response['arguments'][0].get('messages'):
481
287
message = response['arguments'][0]['messages'][0]
@@ -488,9 +294,11 @@ async def stream_generate(
488
294
inline_txt = card['inlines'][0].get('text')
489
295
response_txt += inline_txt + '\n'
490
296
elif message.get('contentType') == "IMAGE":
491
query = urllib.parse.quote(message.get('text'))
492
url = f"\nhttps://www.bing.com/images/create?q={query}"
493
response_txt += url
297
prompt = message.get('text')
298
try:
299
response_txt += format_images_markdown(await create_images(session, prompt, proxy), prompt)
300
except:
301
response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}"
494
302
final = True
495
303
if response_txt.startswith(returned_text):
496
304
new = response_txt[len(returned_text):]
@@ -500,7 +308,18 @@ async def stream_generate(
500
308
elif response.get('type') == 2:
501
309
result = response['item']['result']
502
310
if result.get('error'):
503
raise Exception(f"{result['value']}: {result['message']}")
311
if result["value"] == "CaptchaChallenge":
312
driver = get_browser(proxy=proxy)
313
try:
314
for chunk in wait_for_login(driver):
315
yield chunk
316
cookies = get_driver_cookies(driver)
317
finally:
318
driver.quit()
319
async for chunk in stream_generate(prompt, tone, image, context, proxy, cookies, web_search, gpt4_turbo):
320
yield chunk
321
else:
322
raise Exception(f"{result['value']}: {result['message']}")
504
323
return
505
324
finally:
506
325
await delete_conversation(session, conversation, proxy)
@@ -0,0 +1,68 @@
1
from __future__ import annotations
2
3
import json
4
from aiohttp import ClientSession
5
6
from ..typing import AsyncResult, Messages
7
from .base_provider import AsyncGeneratorProvider
8
9
10
class FreeChatgpt(AsyncGeneratorProvider):
11
url = "https://free.chatgpt.org.uk"
12
working = True
13
supports_gpt_35_turbo = True
14
supports_gpt_4 = True
15
supports_message_history = True
16
17
@classmethod
18
async def create_async_generator(
19
cls,
20
model: str,
21
messages: Messages,
22
proxy: str = None,
23
**kwargs
24
) -> AsyncResult:
25
if not model:
26
model = "gpt-3.5-turbo"
27
headers = {
28
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0",
29
"Accept": "application/json, text/event-stream",
30
"Accept-Language": "de,en-US;q=0.7,en;q=0.3",
31
"Accept-Encoding": "gzip, deflate, br",
32
"Content-Type": "application/json",
33
"Referer": "https://free.chatgpt.org.uk/",
34
"x-requested-with": "XMLHttpRequest",
35
"Origin": "https://free.chatgpt.org.uk",
36
"Sec-Fetch-Dest": "empty",
37
"Sec-Fetch-Mode": "cors",
38
"Sec-Fetch-Site": "same-origin",
39
"Connection": "keep-alive",
40
"Alt-Used": "free.chatgpt.org.uk",
41
"Pragma": "no-cache",
42
"Cache-Control": "no-cache",
43
"TE": "trailers",
44
}
45
async with ClientSession(headers=headers) as session:
46
data = {
47
"messages": messages,
48
"isAzure": False,
49
"azureApiVersion": "2023-08-01-preview",
50
"stream": True,
51
"model": model,
52
"temperature": 0.5,
53
"presence_penalty": 0,
54
"frequency_penalty": 0,
55
"top_p": 1,
56
"baseUrl": "/api/openai",
57
"maxIterations": 10,
58
"returnIntermediateSteps": True,
59
"useTools": ["web-search", "calculator", "web-browser"],
60
**kwargs
61
}
62
async with session.post(f"{cls.url}/api/langchain/tool/agent/nodejs", json=data, proxy=proxy) as response:
63
response.raise_for_status()
64
async for line in response.content:
65
if line.startswith(b"data: "):
66
data = json.loads(line[6:])
67
if data["isSuccess"] and not data["isToolMessage"]:
68
yield data["message"]
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
from ..base_provider import BaseProvider, ProviderType
4
4
from .retry_provider import RetryProvider
5
5
from .base_provider import AsyncProvider, AsyncGeneratorProvider
6
from .create_images import CreateImagesProvider
6
7
from .deprecated import *
7
8
from .needs_auth import *
8
9
from .unfinished import *
@@ -30,6 +31,7 @@ from .ChatgptX import ChatgptX
30
31
from .Chatxyz import Chatxyz
31
32
from .DeepInfra import DeepInfra
32
33
from .FakeGpt import FakeGpt
34
from .FreeChatgpt import FreeChatgpt
33
35
from .FreeGpt import FreeGpt
34
36
from .GeekGpt import GeekGpt
35
37
from .GeminiProChat import GeminiProChat
@@ -0,0 +1,43 @@
1
from aiohttp import ClientSession
2
3
4
class Conversation():
5
def __init__(self, conversationId: str, clientId: str, conversationSignature: str) -> None:
6
self.conversationId = conversationId
7
self.clientId = clientId
8
self.conversationSignature = conversationSignature
9
10
async def create_conversation(session: ClientSession, proxy: str = None) -> Conversation:
11
url = 'https://www.bing.com/turing/conversation/create?bundleVersion=1.1199.4'
12
async with session.get(url, proxy=proxy) as response:
13
data = await response.json()
14
15
conversationId = data.get('conversationId')
16
clientId = data.get('clientId')
17
conversationSignature = response.headers.get('X-Sydney-Encryptedconversationsignature')
18
19
if not conversationId or not clientId or not conversationSignature:
20
raise Exception('Failed to create conversation.')
21
return Conversation(conversationId, clientId, conversationSignature)
22
23
async def list_conversations(session: ClientSession) -> list:
24
url = "https://www.bing.com/turing/conversation/chats"
25
async with session.get(url) as response:
26
response = await response.json()
27
return response["chats"]
28
29
async def delete_conversation(session: ClientSession, conversation: Conversation, proxy: str = None) -> list:
30
url = "https://sydney.bing.com/sydney/DeleteSingleConversation"
31
json = {
32
"conversationId": conversation.conversationId,
33
"conversationSignature": conversation.conversationSignature,
34
"participant": {"id": conversation.clientId},
35
"source": "cib",
36
"optionsSets": ["autosave"]
37
}
38
try:
39
async with session.post(url, json=json, proxy=proxy) as response:
40
response = await response.json()
41
return response["result"]["value"] == "Success"
42
except:
43
return False
@@ -0,0 +1,164 @@
1
import asyncio
2
import time, json, os
3
from aiohttp import ClientSession
4
from bs4 import BeautifulSoup
5
from urllib.parse import quote
6
from typing import Generator
7
8
from ..create_images import CreateImagesProvider
9
from ..helper import get_cookies, get_event_loop
10
from ...webdriver import WebDriver, get_driver_cookies, get_browser
11
from ...base_provider import ProviderType
12
13
BING_URL = "https://www.bing.com"
14
15
def wait_for_login(driver: WebDriver, timeout: int = 1200) -> None:
16
driver.get(f"{BING_URL}/")
17
value = driver.get_cookie("_U")
18
if value:
19
return
20
start_time = time.time()
21
while True:
22
if time.time() - start_time > timeout:
23
raise RuntimeError("Timeout error")
24
value = driver.get_cookie("_U")
25
if value:
26
return
27
time.sleep(0.5)
28
29
def create_session(cookies: dict) -> ClientSession:
30
headers = {
31
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
32
"accept-encoding": "gzip, deflate, br",
33
"accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh-TW;q=0.7,zh;q=0.6",
34
"content-type": "application/x-www-form-urlencoded",
35
"referrer-policy": "origin-when-cross-origin",
36
"referrer": "https://www.bing.com/images/create/",
37
"origin": "https://www.bing.com",
38
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54",
39
"sec-ch-ua": "\"Microsoft Edge\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"",
40
"sec-ch-ua-mobile": "?0",
41
"sec-fetch-dest": "document",
42
"sec-fetch-mode": "navigate",
43
"sec-fetch-site": "same-origin",
44
"sec-fetch-user": "?1",
45
"upgrade-insecure-requests": "1",
46
}
47
if cookies:
48
headers["cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items())
49
return ClientSession(headers=headers)
50
51
async def create_images(session: ClientSession, prompt: str, proxy: str = None, timeout: int = 300) -> list:
52
url_encoded_prompt = quote(prompt)
53
payload = f"q={url_encoded_prompt}&rt=4&FORM=GENCRE"
54
url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE"
55
async with session.post(
56
url,
57
allow_redirects=False,
58
data=payload,
59
timeout=timeout,
60
) as response:
61
response.raise_for_status()
62
errors = [
63
"this prompt is being reviewed",
64
"this prompt has been blocked",
65
"we're working hard to offer image creator in more languages"
66
]
67
text = (await response.text()).lower()
68
for error in errors:
69
if error in text:
70
raise RuntimeError(f"Create images failed: {error}")
71
if response.status != 302:
72
url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
73
async with session.post(url, allow_redirects=False, proxy=proxy, timeout=timeout) as response:
74
if response.status != 302:
75
raise RuntimeError(f"Create images failed. Status Code: {response.status}")
76
77
redirect_url = response.headers["Location"].replace("&nfy=1", "")
78
redirect_url = f"{BING_URL}{redirect_url}"
79
request_id = redirect_url.split("id=")[1]
80
async with session.get(redirect_url) as response:
81
response.raise_for_status()
82
83
polling_url = f"{BING_URL}/images/create/async/results/{request_id}?q={url_encoded_prompt}"
84
start_time = time.time()
85
while True:
86
if time.time() - start_time > timeout:
87
raise RuntimeError(f"Timeout error after {timeout} seconds")
88
async with session.get(polling_url) as response:
89
if response.status != 200:
90
raise RuntimeError(f"Polling images faild. Status Code: {response.status}")
91
text = await response.text()
92
if not text:
93
await asyncio.sleep(1)
94
else:
95
break
96
error = None
97
try:
98
error = json.loads(text).get("errorMessage")
99
except:
100
pass
101
if error == "Pending":
102
raise RuntimeError("Prompt is been blocked")
103
elif error:
104
raise RuntimeError(error)
105
return read_images(text)
106
107
def read_images(text: str) -> list:
108
html_soup = BeautifulSoup(text, "html.parser")
109
tags = html_soup.find_all("img")
110
image_links = [img["src"] for img in tags if "mimg" in img["class"]]
111
images = [link.split("?w=")[0] for link in image_links]
112
bad_images = [
113
"https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
114
"https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
115
]
116
if any(im in bad_images for im in images):
117
raise RuntimeError("Bad images found")
118
if not images:
119
raise RuntimeError("No images found")
120
return images
121
122
def format_images_markdown(images: list, prompt: str) -> str:
123
images = [f"[]({image})" for idx, image in enumerate(images)]
124
images = "\n".join(images)
125
start_flag = "<!-- generated images start -->\n"
126
end_flag = "<!-- generated images end -->\n"
127
return f"\n{start_flag}{images}\n{end_flag}\n"
128
129
async def create_images_markdown(cookies: dict, prompt: str, proxy: str = None) -> str:
130
session = create_session(cookies)
131
try:
132
images = await create_images(session, prompt, proxy)
133
return format_images_markdown(images, prompt)
134
finally:
135
await session.close()
136
137
def get_cookies_from_browser(proxy: str = None) -> dict:
138
driver = get_browser(proxy=proxy)
139
try:
140
wait_for_login(driver)
141
return get_driver_cookies(driver)
142
finally:
143
driver.quit()
144
145
def create_completion(prompt: str, cookies: dict = None, proxy: str = None) -> Generator:
146
loop = get_event_loop()
147
if not cookies:
148
cookies = get_cookies(".bing.com")
149
if "_U" not in cookies:
150
login_url = os.environ.get("G4F_LOGIN_URL")
151
if login_url:
152
yield f"Please login: [Bing]({login_url})\n\n"
153
cookies = get_cookies_from_browser(proxy)
154
yield loop.run_until_complete(create_images_markdown(cookies, prompt, proxy))
155
156
async def create_async(prompt: str, cookies: dict = None, proxy: str = None) -> str:
157
if not cookies:
158
cookies = get_cookies(".bing.com")
159
if "_U" not in cookies:
160
cookies = get_cookies_from_browser(proxy)
161
return await create_images_markdown(cookies, prompt, proxy)
162
163
def patch_provider(provider: ProviderType) -> CreateImagesProvider:
164
return CreateImagesProvider(provider, create_completion, create_async)
@@ -0,0 +1,162 @@
1
from __future__ import annotations
2
3
import string
4
import random
5
import json
6
import re
7
import io
8
import base64
9
import numpy as np
10
from PIL import Image
11
from aiohttp import ClientSession
12
13
async def upload_image(
14
session: ClientSession,
15
image: str,
16
tone: str,
17
proxy: str = None
18
):
19
try:
20
image_config = {
21
"maxImagePixels": 360000,
22
"imageCompressionRate": 0.7,
23
"enableFaceBlurDebug": 0,
24
}
25
is_data_uri_an_image(image)
26
img_binary_data = extract_data_uri(image)
27
is_accepted_format(img_binary_data)
28
img = Image.open(io.BytesIO(img_binary_data))
29
width, height = img.size
30
max_image_pixels = image_config['maxImagePixels']
31
if max_image_pixels / (width * height) < 1:
32
new_width = int(width * np.sqrt(max_image_pixels / (width * height)))
33
new_height = int(height * np.sqrt(max_image_pixels / (width * height)))
34
else:
35
new_width = width
36
new_height = height
37
try:
38
orientation = get_orientation(img)
39
except Exception:
40
orientation = None
41
new_img = process_image(orientation, img, new_width, new_height)
42
new_img_binary_data = compress_image_to_base64(new_img, image_config['imageCompressionRate'])
43
data, boundary = build_image_upload_api_payload(new_img_binary_data, tone)
44
headers = session.headers.copy()
45
headers["content-type"] = f'multipart/form-data; boundary={boundary}'
46
headers["referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
47
headers["origin"] = 'https://www.bing.com'
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.")
51
image_info = await response.json()
52
if not image_info.get('blobId'):
53
raise RuntimeError("Failed to parse image info.")
54
result = {'bcid': image_info.get('blobId', "")}
55
result['blurredBcid'] = image_info.get('processedBlobId', "")
56
if result['blurredBcid'] != "":
57
result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['blurredBcid']
58
elif result['bcid'] != "":
59
result["imageUrl"] = "https://www.bing.com/images/blob?bcid=" + result['bcid']
60
result['originalImageUrl'] = (
61
"https://www.bing.com/images/blob?bcid="
62
+ result['blurredBcid']
63
if image_config["enableFaceBlurDebug"]
64
else "https://www.bing.com/images/blob?bcid="
65
+ result['bcid']
66
)
67
return result
68
except Exception as e:
69
raise RuntimeError(f"Upload image failed: {e}")
70
71
72
def build_image_upload_api_payload(image_bin: str, tone: str):
73
payload = {
74
'invokedSkills': ["ImageById"],
75
'subscriptionId': "Bing.Chat.Multimodal",
76
'invokedSkillsRequestData': {
77
'enableFaceBlur': True
78
},
79
'convoData': {
80
'convoid': "",
81
'convotone': tone
82
}
83
}
84
knowledge_request = {
85
'imageInfo': {},
86
'knowledgeRequest': payload
87
}
88
boundary="----WebKitFormBoundary" + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
89
data = (
90
f'--{boundary}'
91
+ '\r\nContent-Disposition: form-data; name="knowledgeRequest"\r\n\r\n'
92
+ json.dumps(knowledge_request, ensure_ascii=False)
93
+ "\r\n--"
94
+ boundary
95
+ '\r\nContent-Disposition: form-data; name="imageBase64"\r\n\r\n'
96
+ image_bin
97
+ "\r\n--"
98
+ boundary
99
+ "--\r\n"
100
)
101
return data, boundary
102
103
def is_data_uri_an_image(data_uri: str):
104
# Check if the data URI starts with 'data:image' and contains an image format (e.g., jpeg, png, gif)
105
if not re.match(r'data:image/(\w+);base64,', data_uri):
106
raise ValueError("Invalid data URI image.")
107
# Extract the image format from the data URI
108
image_format = re.match(r'data:image/(\w+);base64,', data_uri).group(1)
109
# Check if the image format is one of the allowed formats (jpg, jpeg, png, gif)
110
if image_format.lower() not in ['jpeg', 'jpg', 'png', 'gif']:
111
raise ValueError("Invalid image format (from mime file type).")
112
113
def is_accepted_format(binary_data: bytes) -> bool:
114
if binary_data.startswith(b'\xFF\xD8\xFF'):
115
pass # It's a JPEG image
116
elif binary_data.startswith(b'\x89PNG\r\n\x1a\n'):
117
pass # It's a PNG image
118
elif binary_data.startswith(b'GIF87a') or binary_data.startswith(b'GIF89a'):
119
pass # It's a GIF image
120
elif binary_data.startswith(b'\x89JFIF') or binary_data.startswith(b'JFIF\x00'):
121
pass # It's a JPEG image
122
elif binary_data.startswith(b'\xFF\xD8'):
123
pass # It's a JPEG image
124
elif binary_data.startswith(b'RIFF') and binary_data[8:12] == b'WEBP':
125
pass # It's a WebP image
126
else:
127
raise ValueError("Invalid image format (from magic code).")
128
129
def extract_data_uri(data_uri: str) -> bytes:
130
data = data_uri.split(",")[1]
131
data = base64.b64decode(data)
132
return data
133
134
def get_orientation(data: bytes) -> int:
135
if data[:2] != b'\xFF\xD8':
136
raise Exception('NotJpeg')
137
with Image.open(data) as img:
138
exif_data = img._getexif()
139
if exif_data is not None:
140
orientation = exif_data.get(274) # 274 corresponds to the orientation tag in EXIF
141
if orientation is not None:
142
return orientation
143
144
def process_image(orientation: int, img: Image.Image, new_width: int, new_height: int) -> Image.Image:
145
# Initialize the canvas
146
new_img = Image.new("RGB", (new_width, new_height), color="#FFFFFF")
147
if orientation:
148
if orientation > 4:
149
img = img.transpose(Image.FLIP_LEFT_RIGHT)
150
if orientation in [3, 4]:
151
img = img.transpose(Image.ROTATE_180)
152
if orientation in [5, 6]:
153
img = img.transpose(Image.ROTATE_270)
154
if orientation in [7, 8]:
155
img = img.transpose(Image.ROTATE_90)
156
new_img.paste(img, (0, 0))
157
return new_img
158
159
def compress_image_to_base64(image: Image.Image, compression_rate: float) -> str:
160
output_buffer = io.BytesIO()
161
image.save(output_buffer, format="JPEG", quality=int(compression_rate * 100))
162
return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
@@ -0,0 +1,84 @@
1
from __future__ import annotations
2
3
import re
4
import asyncio
5
from ..typing import CreateResult, Messages
6
from ..base_provider import BaseProvider, ProviderType
7
8
system_message = """
9
You can generate custom images with the DALL-E 3 image generator.
10
To generate a image with a prompt, do this:
11
<img data-prompt=\"keywords for the image\">
12
Don't use images with data uri. It is important to use a prompt instead.
13
<img data-prompt=\"image caption\">
14
"""
15
16
class CreateImagesProvider(BaseProvider):
17
def __init__(
18
self,
19
provider: ProviderType,
20
create_images: callable,
21
create_async: callable,
22
system_message: str = system_message,
23
include_placeholder: bool = True
24
) -> None:
25
self.provider = provider
26
self.create_images = create_images
27
self.create_images_async = create_async
28
self.system_message = system_message
29
self.__name__ = provider.__name__
30
self.working = provider.working
31
self.supports_stream = provider.supports_stream
32
self.include_placeholder = include_placeholder
33
if hasattr(provider, "url"):
34
self.url = provider.url
35
36
def create_completion(
37
self,
38
model: str,
39
messages: Messages,
40
stream: bool = False,
41
**kwargs
42
) -> CreateResult:
43
messages.insert(0, {"role": "system", "content": self.system_message})
44
buffer = ""
45
for chunk in self.provider.create_completion(model, messages, stream, **kwargs):
46
if buffer or "<" in chunk:
47
buffer += chunk
48
if ">" in buffer:
49
match = re.search(r'<img data-prompt="(.*?)">', buffer)
50
if match:
51
placeholder, prompt = match.group(0), match.group(1)
52
start, append = buffer.split(placeholder, 1)
53
if start:
54
yield start
55
if self.include_placeholder:
56
yield placeholder
57
yield from self.create_images(prompt)
58
if append:
59
yield append
60
else:
61
yield buffer
62
buffer = ""
63
else:
64
yield chunk
65
66
async def create_async(
67
self,
68
model: str,
69
messages: Messages,
70
**kwargs
71
) -> str:
72
messages.insert(0, {"role": "system", "content": self.system_message})
73
response = await self.provider.create_async(model, messages, **kwargs)
74
matches = re.findall(r'(<img data-prompt="(.*?)">)', result)
75
results = []
76
for _, prompt in matches:
77
results.append(self.create_images_async(prompt))
78
results = await asyncio.gather(*results)
79
for idx, result in enumerate(results):
80
placeholder = matches[idx][0]
81
if self.include_placeholder:
82
result = placeholder + result
83
response = response.replace(placeholder, result)
84
return result
@@ -64,11 +64,14 @@ XMLHttpRequest.prototype.open = function(method, url) {
64
64
"""
65
65
driver.execute_script(script)
66
66
67
# Submit prompt
68
driver.find_element(By.CSS_SELECTOR, "div.ql-editor.textarea").send_keys(prompt)
69
driver.find_element(By.CSS_SELECTOR, "div.ql-editor.textarea").send_keys(Keys.ENTER)
70
71
# Yield response
67
textarea = driver.find_element(By.CSS_SELECTOR, "div.ql-editor.textarea")
68
lines = prompt.splitlines()
69
for idx, line in enumerate(lines):
70
textarea.send_keys(line)
71
if (len(lines) - 1 != idx):
72
textarea.send_keys(Keys.SHIFT + "\n")
73
textarea.send_keys(Keys.ENTER)
74
72
75
while True:
73
76
chunk = driver.execute_script("return window._message;")
74
77
if chunk:
@@ -68,6 +68,7 @@ class ChatCompletion:
68
68
ignored : list[str] = None,
69
69
ignore_working: bool = False,
70
70
ignore_stream_and_auth: bool = False,
71
patch_provider: callable = None,
71
72
**kwargs) -> Union[CreateResult, str]:
72
73
73
74
model, provider = get_model_and_provider(model, provider, stream, ignored, ignore_working, ignore_stream_and_auth)
@@ -83,6 +84,9 @@ class ChatCompletion:
83
84
if proxy:
84
85
kwargs['proxy'] = proxy
85
86
87
if patch_provider:
88
provider = patch_provider(provider)
89
86
90
result = provider.create_completion(model, messages, stream, **kwargs)
87
91
return result if stream else ''.join(result)
88
92
@@ -92,6 +96,7 @@ class ChatCompletion:
92
96
provider : Union[ProviderType, str, None] = None,
93
97
stream : bool = False,
94
98
ignored : list[str] = None,
99
patch_provider: callable = None,
95
100
**kwargs) -> Union[AsyncResult, str]:
96
101
97
102
model, provider = get_model_and_provider(model, provider, False, ignored)
@@ -101,6 +106,9 @@ class ChatCompletion:
101
106
return provider.create_async_generator(model, messages, **kwargs)
102
107
raise StreamNotSupportedError(f'{provider.__name__} does not support "stream" argument in "create_async"')
103
108
109
if patch_provider:
110
provider = patch_provider(provider)
111
104
112
return provider.create_async(model, messages, **kwargs)
105
113
106
114
class Completion:
@@ -124,6 +124,11 @@
124
124
<label for="switch"></label>
125
125
<span class="about">Web Access</span>
126
126
</div>
127
<div class="field">
128
<input type="checkbox" id="patch" />
129
<label for="patch" title="Works only with Bing and some other providers"></label>
130
<span class="about">Image Generator</span>
131
</div>
127
132
<div class="field">
128
133
<select name="model" id="model">
129
134
<option value="gpt-3.5-turbo" selected="">gpt-3.5-turbo</option>
@@ -20,7 +20,10 @@ message_input.addEventListener("focus", () => {
20
20
});
21
21
22
22
const markdown_render = (content) => {
23
return markdown.render(content)
23
return markdown.render(content
24
.replaceAll(/<!--.+-->/gm, "")
25
.replaceAll(/<img data-prompt="[^>]+">/gm, "")
26
)
24
27
.replaceAll("<a href=", '<a target="_blank" href=')
25
28
.replaceAll('<code>', '<code class="language-plaintext">')
26
29
}
@@ -68,6 +71,15 @@ const ask_gpt = async () => {
68
71
regenerate.classList.add(`regenerate-hidden`);
69
72
messages = await get_messages(window.conversation_id);
70
73
74
// Remove generated images from history
75
for (i in messages) {
76
messages[i]["content"] = messages[i]["content"].replace(
77
/<!-- generated images start -->[\s\S]+<!-- generated images end -->/m,
78
""
79
)
80
delete messages[i]["provider"];
81
}
82
71
83
window.scrollTo(0, 0);
72
84
window.controller = new AbortController();
73
85
@@ -91,7 +103,8 @@ const ask_gpt = async () => {
91
103
</div>
92
104
<div class="content" id="gpt_${window.token}">
93
105
<div class="provider"></div>
94
<div class="content_inner"><div id="cursor"></div></div>
106
<div class="content_inner"></div>
107
<div id="cursor"></div>
95
108
</div>
96
109
</div>
97
110
`;
@@ -115,6 +128,7 @@ const ask_gpt = async () => {
115
128
jailbreak: jailbreak.options[jailbreak.selectedIndex].value,
116
129
internet_access: document.getElementById(`switch`).checked,
117
130
provider: provider.options[provider.selectedIndex].value,
131
patch_provider: document.getElementById('patch').checked,
118
132
meta: {
119
133
id: window.token,
120
134
content: {
@@ -163,8 +177,6 @@ const ask_gpt = async () => {
163
177
} catch (e) {
164
178
console.log(e);
165
179
166
let cursorDiv = document.getElementById(`cursor`);
167
if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv);
168
180
169
181
if (e.name != `AbortError`) {
170
182
text = `oops ! something went wrong, please try again / reload. [stacktrace in console]`;
@@ -174,6 +186,8 @@ const ask_gpt = async () => {
174
186
text += ` [aborted]`
175
187
}
176
188
}
189
let cursorDiv = document.getElementById(`cursor`);
190
if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv);
177
191
add_message(window.conversation_id, "assistant", text, provider);
178
192
message_box.scrollTop = message_box.scrollHeight;
179
193
await remove_cancel_button();
@@ -430,7 +444,7 @@ document.querySelector(".mobile-sidebar").addEventListener("click", (event) => {
430
444
});
431
445
432
446
const register_settings_localstorage = async () => {
433
settings_ids = ["switch", "model", "jailbreak"];
447
settings_ids = ["switch", "model", "jailbreak", "patch", "provider"];
434
448
settings_elements = settings_ids.map((id) => document.getElementById(id));
435
449
settings_elements.map((element) =>
436
450
element.addEventListener(`change`, async (event) => {
@@ -449,7 +463,7 @@ const register_settings_localstorage = async () => {
449
463
};
450
464
451
465
const load_settings_localstorage = async () => {
452
settings_ids = ["switch", "model", "jailbreak"];
466
settings_ids = ["switch", "model", "jailbreak", "patch", "provider"];
453
467
settings_elements = settings_ids.map((id) => document.getElementById(id));
454
468
settings_elements.map((element) => {
455
469
if (localStorage.getItem(element.id)) {