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

XFEstudio/gpt4free

Add create images to Bing (#1426)

Add create images from Bing Add FreeChatgpt Provider Fix Bard Provider

bee75be8
H Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

19 个文件 +673 -308
Modified g4f/Provider/Bestim.py +17 -26
@@ -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
Modified g4f/Provider/Bing.py +67 -248
@@ -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)
Added g4f/Provider/FreeChatgpt.py +68 -0
@@ -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"]
Modified g4f/Provider/__init__.py +2 -0
@@ -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
Added g4f/Provider/bing/conversation.py +43 -0
@@ -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
Added g4f/Provider/bing/create_images.py +164 -0
@@ -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"[![#{idx+1} {prompt}]({image}?w=200&h=200)]({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)
Added g4f/Provider/bing/upload_image.py +162 -0
@@ -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')
Added g4f/Provider/create_images.py +84 -0
@@ -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
Modified g4f/Provider/needs_auth/Bard.py +8 -5
@@ -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:
Modified g4f/__init__.py +8 -0
@@ -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:
Modified g4f/gui/client/html/index.html +5 -0
@@ -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>
Modified g4f/gui/client/js/chat.v1.js +20 -6
@@ -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)) {
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