返回提交历史
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+77
-36
Modified
g4f/image/__init__.py
+118
-0
XFEstudio/gpt4free
add upload_file
- add upload_file - add conversation_mode - add temporary
fe24210d
代码差异
2 个文件
+195
-36
@@ -22,7 +22,7 @@ from ...typing import AsyncResult, Messages, Cookies, MediaListType
22
22
from ...requests.raise_for_status import raise_for_status
23
23
from ...requests import StreamSession
24
24
from ...requests import get_nodriver
25
from ...image import ImageRequest, to_image, to_bytes, is_accepted_format
25
from ...image import ImageRequest, to_image, to_bytes, is_accepted_format, detect_file_type
26
26
from ...errors import MissingAuthError, NoValidHarFileError, ModelNotFoundError
27
27
from ...providers.response import JsonConversation, FinishReason, SynthesizeData, AuthResult, ImageResponse, ImagePreview, ResponseType, format_link
28
28
from ...providers.response import TitleGeneration, RequestLogin, Reasoning
@@ -126,54 +126,66 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
126
126
)
127
127
128
128
@classmethod
129
async def upload_images(
129
async def upload_files(
130
130
cls,
131
131
session: StreamSession,
132
132
auth_result: AuthResult,
133
133
media: MediaListType,
134
) -> ImageRequest:
134
) -> list[ImageRequest]:
135
135
"""
136
136
Upload an image to the service and get the download URL
137
137
138
138
Args:
139
139
session: The StreamSession object to use for requests
140
140
headers: The headers to include in the requests
141
media: The images to upload, either a PIL Image object or a bytes object
141
media: The files to upload, either a PIL Image object or a bytes object
142
142
143
143
Returns:
144
144
An ImageRequest object that contains the download URL, file name, and other data
145
145
"""
146
async def upload_image(image, image_name):
147
debug.log(f"Uploading image: {image_name}")
148
# Convert the image to a PIL Image object and get the extension
149
data_bytes = to_bytes(image)
150
image = to_image(data_bytes)
151
extension = image.format.lower()
146
async def upload_file(file, image_name=None):
147
debug.log(f"Uploading file: {image_name}")
148
file_data = {}
149
150
data_bytes = to_bytes(file)
151
extension, mime_type = detect_file_type(data_bytes)
152
if "image" in mime_type:
153
# Convert the image to a PIL Image object
154
file = to_image(data_bytes)
155
use_case = "multimodal"
156
file_data.update({"height": file.height, "width": file.width})
157
else:
158
use_case = "my_files"
159
image_name = (
160
f"file-{len(data_bytes)}{extension}"
161
if image_name is None
162
else image_name
163
)
152
164
data = {
153
"file_name": "" if image_name is None else image_name,
165
"file_name": image_name,
154
166
"file_size": len(data_bytes),
155
"use_case": "multimodal"
167
"use_case": use_case,
156
168
}
157
169
# Post the image data to the service and get the image data
158
170
async with session.post(f"{cls.url}/backend-api/files", json=data, headers=cls._headers) as response:
159
171
cls._update_request_args(auth_result, session)
160
172
await raise_for_status(response, "Create file failed")
161
image_data = {
162
**data,
163
**await response.json(),
164
"mime_type": is_accepted_format(data_bytes),
165
"extension": extension,
166
"height": image.height,
167
"width": image.width
168
}
173
file_data.update(
174
{
175
**data,
176
**await response.json(),
177
"mime_type": mime_type,
178
"extension": extension,
179
}
180
)
169
181
# Put the image bytes to the upload URL and check the status
170
182
await asyncio.sleep(1)
171
183
async with session.put(
172
image_data["upload_url"],
184
file_data["upload_url"],
173
185
data=data_bytes,
174
186
headers={
175
187
**UPLOAD_HEADERS,
176
"Content-Type": image_data["mime_type"],
188
"Content-Type": file_data["mime_type"],
177
189
"x-ms-blob-type": "BlockBlob",
178
190
"x-ms-version": "2020-04-08",
179
191
"Origin": "https://chatgpt.com",
@@ -182,15 +194,22 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
182
194
await raise_for_status(response)
183
195
# Post the file ID to the service and get the download URL
184
196
async with session.post(
185
f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded",
197
f"{cls.url}/backend-api/files/{file_data['file_id']}/uploaded",
186
198
json={},
187
199
headers=auth_result.headers
188
200
) as response:
189
201
cls._update_request_args(auth_result, session)
190
202
await raise_for_status(response, "Get download url failed")
191
image_data["download_url"] = (await response.json())["download_url"]
192
return ImageRequest(image_data)
193
return [await upload_image(image, image_name) for image, image_name in media]
203
uploaded_data = await response.json()
204
file_data["download_url"] = uploaded_data["download_url"]
205
return ImageRequest(file_data)
206
207
medias = []
208
for item in media:
209
item = item if isinstance(item, tuple) else (item,)
210
__uploaded_media = await upload_file(*item)
211
medias.append(__uploaded_media)
212
return medias
194
213
195
214
@classmethod
196
215
def create_messages(cls, messages: Messages, image_requests: ImageRequest = None, system_hints: list = None):
@@ -237,18 +256,27 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
237
256
"size_bytes": image_request.get("file_size"),
238
257
"width": image_request.get("width"),
239
258
}
240
for image_request in image_requests],
259
for image_request in image_requests
260
# Add For Images Only
261
if image_request.get("use_case") == "multimodal"
262
],
241
263
messages[-1]["content"]["parts"][0]]
242
264
}
243
265
# Add the metadata object with the attachments
244
266
messages[-1]["metadata"] = {
245
267
"attachments": [{
246
"height": image_request.get("height"),
247
268
"id": image_request.get("file_id"),
248
269
"mimeType": image_request.get("mime_type"),
249
270
"name": image_request.get("file_name"),
250
271
"size": image_request.get("file_size"),
251
"width": image_request.get("width"),
272
**(
273
{
274
"height": image_request.get("height"),
275
"width": image_request.get("width"),
276
}
277
if image_request.get("use_case") == "multimodal"
278
else {}
279
),
252
280
}
253
281
for image_request in image_requests]
254
282
}
@@ -308,6 +336,8 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
308
336
return_conversation: bool = True,
309
337
web_search: bool = False,
310
338
prompt: str = None,
339
conversation_mode=None,
340
temporary=False,
311
341
**kwargs
312
342
) -> AsyncResult:
313
343
"""
@@ -353,11 +383,12 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
353
383
async with session.get(cls.url, headers=cls._headers) as response:
354
384
cls._update_request_args(auth_result, session)
355
385
await raise_for_status(response)
356
try:
357
image_requests = await cls.upload_images(session, auth_result, media)
358
except Exception as e:
359
debug.error("OpenaiChat: Upload image failed")
360
debug.error(e)
386
387
# try:
388
image_requests = await cls.upload_files(session, auth_result, media)
389
# except Exception as e:
390
# debug.error("OpenaiChat: Upload image failed")
391
# debug.error(e)
361
392
try:
362
393
model = cls.get_model(model)
363
394
except ModelNotFoundError:
@@ -370,6 +401,10 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
370
401
conversation = Conversation(None, str(uuid.uuid4()), getattr(auth_result, "cookies", {}).get("oai-did"))
371
402
else:
372
403
conversation = copy(conversation)
404
405
if conversation_mode is None:
406
conversation_mode = {"kind": "primary_assistant"}
407
373
408
if getattr(auth_result, "cookies", {}).get("oai-did") != getattr(conversation, "user_id", None):
374
409
conversation = Conversation(None, str(uuid.uuid4()))
375
410
if cls._api_key is None:
@@ -394,6 +429,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
394
429
"supports_buffering": True,
395
430
"supported_encodings": ["v1"]
396
431
}
432
if temporary:
433
data["history_and_training_disabled"] = True
434
397
435
async with session.post(
398
436
prepare_url,
399
437
json=data,
@@ -434,11 +472,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
434
472
user_agent=user_agent,
435
473
proof_token=proof_token
436
474
)
437
[debug.log(text) for text in (
475
# [debug.log(text) for text in (
438
476
#f"Arkose: {'False' if not need_arkose else auth_result.arkose_token[:12]+'...'}",
439
477
#f"Proofofwork: {'False' if proofofwork is None else proofofwork[:12]+'...'}",
440
478
#f"AccessToken: {'False' if cls._api_key is None else cls._api_key[:12]+'...'}",
441
)]
479
# )]
442
480
data = {
443
481
"action": "next",
444
482
"parent_message_id": conversation.message_id,
@@ -453,6 +491,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
453
491
"client_contextual_info":{"is_dark_mode":False,"time_since_loaded":random.randint(20, 500),"page_height":578,"page_width":1850,"pixel_ratio":1,"screen_height":1080,"screen_width":1920},
454
492
"paragen_cot_summary_display_override":"allow"
455
493
}
494
if temporary:
495
data["history_and_training_disabled"] = True
496
456
497
if conversation.conversation_id is not None:
457
498
data["conversation_id"] = conversation.conversation_id
458
499
debug.log(f"OpenaiChat: Use conversation: {conversation.conversation_id}")
@@ -191,6 +191,124 @@ def is_accepted_format(binary_data: bytes) -> str:
191
191
else:
192
192
raise ValueError("Invalid image format (from magic code).")
193
193
194
195
196
def detect_file_type(binary_data: bytes) -> tuple[str, str] | None:
197
"""
198
Detect file type from magic number / header signature.
199
200
Args:
201
binary_data (bytes): File binary data
202
203
Returns:
204
tuple: (extension, MIME type)
205
206
Raises:
207
ValueError: If file type is unknown
208
"""
209
210
# ---- Images ----
211
if binary_data.startswith(b"\xff\xd8\xff"):
212
return ".jpg", "image/jpeg"
213
elif binary_data.startswith(b"\x89PNG\r\n\x1a\n"):
214
return ".png", "image/png"
215
elif binary_data.startswith((b"GIF87a", b"GIF89a")):
216
return ".gif", "image/gif"
217
elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"WEBP":
218
return ".webp", "image/webp"
219
elif binary_data.startswith(b"BM"):
220
return ".bmp", "image/bmp"
221
elif binary_data.startswith(b"II*\x00") or binary_data.startswith(b"MM\x00*"):
222
return ".tiff", "image/tiff"
223
elif binary_data.startswith(b"\x00\x00\x01\x00"):
224
return ".ico", "image/x-icon"
225
elif binary_data.startswith(b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"):
226
return ".jp2", "image/jp2"
227
elif len(binary_data) > 12 and binary_data[4:8] == b"ftyp":
228
brand = binary_data[8:12]
229
if brand in [b"heic", b"heix", b"hevc", b"hevx", b"mif1", b"msf1"]:
230
return ".heic", "image/heif"
231
elif brand in [b"avif"]:
232
return ".avif", "image/avif"
233
elif binary_data.lstrip().startswith((b"<?xml", b"<svg")):
234
return ".svg", "image/svg+xml"
235
236
# ---- Documents ----
237
elif binary_data.startswith(b"%PDF"):
238
return ".pdf", "application/pdf"
239
elif binary_data.startswith(b"PK\x03\x04"):
240
return".zip", "application/zip-based",
241
# could be docx/xlsx/pptx/jar/apk/odt
242
elif binary_data.startswith(b"\xd0\xcf\x11\xe0"):
243
return ".doc", "application/vnd.ms-office"
244
elif binary_data.startswith(b"{\\rtf"):
245
return ".rtf", "application/rtf"
246
elif binary_data.startswith(b"7z\xbc\xaf\x27\x1c"):
247
return ".7z", "application/x-7z-compressed"
248
elif binary_data.startswith(b"Rar!\x1a\x07\x00"):
249
return ".rar", "application/vnd.rar"
250
elif binary_data.startswith(b"\x1f\x8b"):
251
return ".gz", "application/gzip"
252
elif binary_data.startswith(b"BZh"):
253
return ".bz2", "application/x-bzip2"
254
elif binary_data.startswith(b"\xfd7zXZ\x00"):
255
return ".xz", "application/x-xz"
256
257
# ---- Executables / Libraries ----
258
elif binary_data.startswith(b"MZ"):
259
return ".exe", "application/x-msdownload"
260
elif binary_data.startswith(b"\x7fELF"):
261
return ".elf", "application/x-elf"
262
elif binary_data.startswith(b"\xca\xfe\xba\xbe") or binary_data.startswith(
263
b"\xca\xfe\xd0\x0d"
264
):
265
return ".class", "application/java-vm"
266
elif (
267
binary_data.startswith(b"\x50\x4b\x03\x04")
268
and b"META-INF" in binary_data[:200]
269
):
270
return ".jar", "application/java-archive"
271
272
# ---- Audio ----
273
elif binary_data.startswith(b"ID3") or binary_data[0:2] == b"\xff\xfb":
274
return ".mp3", "audio/mpeg"
275
elif binary_data.startswith(b"OggS"):
276
return ".ogg", "audio/ogg"
277
elif binary_data.startswith(b"fLaC"):
278
return ".flac", "audio/flac"
279
elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"WAVE":
280
return ".wav", "audio/wav"
281
elif binary_data.startswith(b"MThd"):
282
return ".mid", "audio/midi"
283
284
# ---- Video ----
285
elif binary_data.startswith(b"\x00\x00\x00") and b"ftyp" in binary_data[4:12]:
286
return ".mp4", "video/mp4"
287
elif binary_data.startswith(b"RIFF") and binary_data[8:12] == b"AVI ":
288
return ".avi", "video/x-msvideo"
289
elif binary_data.startswith(b"OggS"):
290
return ".ogv", "video/ogg"
291
elif binary_data.startswith(b"\x1a\x45\xdf\xa3"):
292
return ".mkv", "video/webm"
293
elif binary_data.startswith(b"\x00\x00\x01\xba"):
294
return ".mpg", "video/mpeg"
295
296
# ---- Text / Scripts ----
297
elif binary_data.lstrip().startswith(b"#!"):
298
return ".sh", "text/x-script"
299
elif binary_data.lstrip().startswith((b"{", b"[")):
300
return ".json", "application/json"
301
elif binary_data.lstrip().startswith((b"<", b"<!DOCTYPE")):
302
return ".html", "text/html"
303
elif binary_data.lstrip().startswith(b"<?xml"):
304
return ".xml", "application/xml"
305
elif all(32 <= b <= 127 or b in (9, 10, 13) for b in binary_data[:100]):
306
return ".txt", "text/plain"
307
308
else:
309
raise ValueError("Unknown or unsupported file type")
310
311
194
312
def extract_data_uri(data_uri: str) -> bytes:
195
313
"""
196
314
Extracts the binary data from the given data URI.