返回提交历史
Modified
g4f/Provider/Copilot.py
+125
-219
Modified
g4f/config.py
+1
-1
XFEstudio/gpt4free
Increase DEFAULT_STREAM_TIMEOUT from 15 to 30 for improved streaming performance
62a0d3c6
代码差异
2 个文件
+126
-220
@@ -10,14 +10,10 @@ import urllib.parse
10
10
from typing import AsyncIterator
11
11
from urllib.parse import quote
12
12
13
try:
14
from curl_cffi.requests import AsyncSession
15
from curl_cffi import CurlWsFlag, CurlMime
16
has_curl_cffi = True
17
except ImportError:
18
has_curl_cffi = False
13
19
14
try:
20
15
import nodriver
16
from nodriver import cdp
21
17
has_nodriver = True
22
18
except ImportError:
23
19
has_nodriver = False
@@ -76,8 +72,6 @@ def get_fake_cookie():
76
72
class Copilot(AsyncAuthedProvider, ProviderModelMixin):
77
73
label = "Microsoft Copilot"
78
74
url = "https://copilot.microsoft.com"
79
cookie_domain = ".microsoft.com"
80
anon_cookie_name = "__Host-copilot-anon"
81
75
82
76
working = True
83
77
use_nodriver = has_nodriver
@@ -92,31 +86,12 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
92
86
"gpt-5": "GPT-5",
93
87
"study": "Study",
94
88
}
95
96
websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
97
conversation_url = f"{url}/c/api/conversations"
89
lock = asyncio.Lock()
90
nodriver = None
98
91
99
92
@classmethod
100
93
async def on_auth_async(cls, cookies: dict = None, proxy: str = None, **kwargs) -> AsyncIterator:
101
if cookies is None:
102
cookies = get_fake_cookie() or get_cookies(cls.cookie_domain, False, cache_result=False)
103
access_token = None
104
useridentitytype = None
105
if cls.needs_auth or cls.anon_cookie_name not in cookies:
106
try:
107
access_token, useridentitytype, cookies = readHAR(cls.url)
108
except NoValidHarFileError as h:
109
debug.log(f"Copilot: {h}")
110
if has_nodriver:
111
yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
112
access_token, useridentitytype, cookies = await get_access_token_and_cookies(cls.url, proxy, cls.needs_auth)
113
else:
114
raise h
115
yield AuthResult(
116
access_token=access_token,
117
useridentitytype=useridentitytype,
118
cookies=cookies
119
)
94
yield AuthResult()
120
95
121
96
@classmethod
122
97
async def create_authed(
@@ -129,202 +104,133 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
129
104
prompt: str = None,
130
105
media: MediaListType = None,
131
106
conversation: BaseConversation = None,
132
return_conversation: bool = True,
133
107
**kwargs
134
108
) -> AsyncResult:
135
if not has_curl_cffi:
136
raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U curl_cffi')
137
model = cls.get_model(model)
138
websocket_url = cls.websocket_url
139
headers = DEFAULT_HEADERS.copy()
140
headers["origin"] = cls.url
141
headers["referer"] = cls.url + "/"
142
if getattr(auth_result, "access_token", None):
143
websocket_url = f"{websocket_url}&accessToken={quote(auth_result.access_token)}" + (f"&X-UserIdentityType={quote(auth_result.useridentitytype)}" if getattr(auth_result, "useridentitytype", None) else "")
144
headers["authorization"] = f"Bearer {auth_result.access_token}"
145
146
async with AsyncSession(
147
timeout=timeout,
148
proxy=proxy,
149
impersonate="chrome",
150
headers=headers,
151
cookies=auth_result.cookies
152
) as session:
153
if conversation is None:
154
# har_file = os.path.join(os.path.dirname(__file__), "copilot", "copilot.microsoft.com.har")
155
# with open(har_file, "r") as f:
156
# har_entries = json.load(f).get("log", {}).get("entries", [])
157
# conversationId = ""
158
# for har_entry in har_entries:
159
# if har_entry.get("request"):
160
# if "/c/api/" in har_entry.get("request").get("url", ""):
161
# try:
162
# response = await getattr(session, har_entry.get("request").get("method").lower())(
163
# har_entry.get("request").get("url", "").replace("cvqBJw7kyPAp1RoMTmzC6", conversationId),
164
# data=har_entry.get("request").get("postData", {}).get("text"),
165
# headers={header["name"]: header["value"] for header in har_entry.get("request").get("headers")}
166
# )
167
# response.raise_for_status()
168
# if response.headers.get("content-type", "").startswith("application/json"):
169
# conversationId = response.json().get("currentConversationId", conversationId)
170
# except Exception as e:
171
# debug.log(f"Copilot: Failed request to {har_entry.get('request').get('url', '')}: {e}")
172
data = {
173
"timeZone": "America/Los_Angeles",
174
"startNewConversation": True,
175
"teenSupportEnabled": True,
176
"correctPersonalizationSetting": True,
177
"performUserMerge": True,
178
"deferredDataUseCapable": True
179
}
180
response = await session.post(
181
"https://copilot.microsoft.com/c/api/start",
182
headers={
183
"content-type": "application/json",
184
**({"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {}),
185
**(headers or {})
186
},
187
json=data
188
)
189
if response.status_code == 401:
190
raise MissingAuthError("Status 401: Invalid session")
191
response.raise_for_status()
192
debug.log(f"Copilot: Update cookies: [{', '.join(key for key in response.cookies)}]")
193
auth_result.cookies.update({key: value for key, value in response.cookies.items()})
194
if not cls.needs_auth and cls.anon_cookie_name not in auth_result.cookies:
195
raise MissingAuthError(f"Missing cookie: {cls.anon_cookie_name}")
196
conversation = Conversation(response.json().get("currentConversationId"))
197
debug.log(f"Copilot: Created conversation: {conversation.conversation_id}")
198
else:
199
debug.log(f"Copilot: Use conversation: {conversation.conversation_id}")
200
201
# response = await session.get("https://copilot.microsoft.com/c/api/user?api-version=4", headers={"x-useridentitytype": useridentitytype} if cls._access_token else {})
202
# if response.status_code == 401:
203
# raise MissingAuthError("Status 401: Invalid session")
204
# response.raise_for_status()
205
# print(response.json())
206
# user = response.json().get('firstName')
207
# if user is None:
208
# if cls.needs_auth:
209
# raise MissingAuthError("No user found, please login first")
210
# cls._access_token = None
211
# else:
212
# debug.log(f"Copilot: User: {user}")
213
214
uploaded_attachments = []
215
if auth_result.access_token:
216
# Upload regular media (images)
217
for media, _ in merge_media(media, messages):
218
if not isinstance(media, str):
219
data = to_bytes(media)
220
response = await session.post(
221
"https://copilot.microsoft.com/c/api/attachments",
222
headers={
223
"content-type": is_accepted_format(data),
224
"content-length": str(len(data)),
225
**({"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {})
226
},
227
data=data
228
)
229
response.raise_for_status()
230
media = response.json().get("url")
231
uploaded_attachments.append({"type":"image", "url": media})
232
233
# Upload bucket files
234
bucket_items = extract_bucket_items(messages)
235
for item in bucket_items:
236
try:
237
# Handle plain text content from bucket
238
bucket_path = Path(get_bucket_dir(item["bucket_id"]))
239
for text_chunk in read_bucket(bucket_path):
240
if text_chunk.strip():
241
# Upload plain text as a text file
242
text_data = text_chunk.encode('utf-8')
243
data = CurlMime()
244
data.addpart("file", filename=f"bucket_{item['bucket_id']}.txt", content_type="text/plain", data=text_data)
245
response = await session.post(
246
"https://copilot.microsoft.com/c/api/attachments",
247
multipart=data,
248
headers={"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {}
249
)
250
response.raise_for_status()
251
data = response.json()
252
uploaded_attachments.append({"type": "document", "attachmentId": data.get("id")})
253
debug.log(f"Copilot: Uploaded bucket text content: {item['bucket_id']}")
254
else:
255
debug.log(f"Copilot: No text content found in bucket: {item['bucket_id']}")
256
except Exception as e:
257
debug.log(f"Copilot: Failed to upload bucket item: {item}")
258
debug.error(e)
259
109
async with cls.lock:
110
if cls.nodriver is None:
111
cls.nodriver, cls.stop_nodriver = await get_nodriver(proxy=proxy)
260
112
if prompt is None:
261
113
prompt = get_last_user_message(messages, False)
262
263
wss = await session.ws_connect(websocket_url, timeout=3)
264
if "Think" in model:
265
mode = "reasoning"
266
elif model.startswith("gpt-5") or "GPT-5" in model:
267
mode = "smart"
114
if conversation is not None:
115
conversation_id = conversation.conversation_id
116
url = f"{cls.url}/chats/{conversation_id}"
268
117
else:
269
mode = "chat"
270
await wss.send(json.dumps({
271
"event": "send",
272
"conversationId": conversation.conversation_id,
273
"content": [*uploaded_attachments, {
274
"type": "text",
275
"text": prompt,
276
}],
277
"mode": mode,
278
}).encode(), CurlWsFlag.TEXT)
118
url = cls.url
119
page = await cls.nodriver.get(url)
120
await page.send(cdp.network.enable())
121
queue = asyncio.Queue()
122
page.add_handler(
123
cdp.network.WebSocketFrameReceived,
124
lambda event: queue.put_nowait((event.request_id, event.response.payload_data)),
125
)
126
textarea = await page.select("textarea")
127
if textarea is not None:
128
await textarea.send_keys(prompt)
129
await asyncio.sleep(1)
130
button = await page.select("[data-testid=\"submit-button\"]")
131
if button:
132
await button.click()
133
turnstile = await page.select('#cf-turnstile')
134
if turnstile:
135
debug.log("Found Element: 'cf-turnstile'")
136
await asyncio.sleep(3)
137
await click_trunstile(page)
138
139
uploaded_attachments = []
140
if auth_result.access_token:
141
# Upload regular media (images)
142
for media, _ in merge_media(media, messages):
143
if not isinstance(media, str):
144
data_bytes = to_bytes(media)
145
response_json = await page.evaluate(f'''
146
fetch('https://copilot.microsoft.com/c/api/attachments', {{
147
method: 'POST',
148
headers: {{
149
'content-type': '{is_accepted_format(data_bytes)}',
150
'content-length': '{len(data_bytes)}',
151
"'x-useridentitytype': '{auth_result.useridentitytype}'," if getattr(auth_result, "useridentitytype", None) else ""
152
}},
153
body: new Uint8Array({list(data_bytes)})
154
}}).then(r => r.json())
155
''')
156
media = response_json.get("url")
157
uploaded_attachments.append({{"type":"image", "url": media}})
279
158
280
done = False
281
msg = None
282
image_prompt: str = None
283
last_msg = None
284
sources = {}
285
while not wss.closed:
159
# Upload bucket files
160
bucket_items = extract_bucket_items(messages)
161
for item in bucket_items:
286
162
try:
287
msg_txt, _ = await asyncio.wait_for(wss.recv(), 1 if done else timeout)
288
msg = json.loads(msg_txt)
289
except:
290
break
291
last_msg = msg
292
if msg.get("event") == "appendText":
293
yield msg.get("text")
294
elif msg.get("event") == "generatingImage":
295
image_prompt = msg.get("prompt")
296
elif msg.get("event") == "imageGenerated":
297
yield ImageResponse(msg.get("url"), image_prompt, {"preview": msg.get("thumbnailUrl")})
298
elif msg.get("event") == "done":
299
yield FinishReason("stop")
300
done = True
301
elif msg.get("event") == "suggestedFollowups":
302
yield SuggestedFollowups(msg.get("suggestions"))
303
break
304
elif msg.get("event") == "replaceText":
305
yield msg.get("text")
306
elif msg.get("event") == "titleUpdate":
307
yield TitleGeneration(msg.get("title"))
308
elif msg.get("event") == "citation":
309
sources[msg.get("url")] = msg
310
yield SourceLink(list(sources.keys()).index(msg.get("url")), msg.get("url"))
311
elif msg.get("event") == "partialImageGenerated":
312
mime_type = is_accepted_format(base64.b64decode(msg.get("content")[:12]))
313
yield ImagePreview(f"data:{mime_type};base64,{msg.get('content')}", image_prompt)
314
elif msg.get("event") == "chainOfThought":
315
yield Reasoning(msg.get("text"))
316
elif msg.get("event") == "error":
317
raise RuntimeError(f"Error: {msg}")
318
elif msg.get("event") not in ["received", "startMessage", "partCompleted", "connected"]:
319
debug.log(f"Copilot Message: {msg_txt[:100]}...")
320
if not done:
321
raise MissingAuthError(f"Invalid response: {last_msg}")
322
if return_conversation:
323
yield conversation
324
if sources:
325
yield Sources(sources.values())
326
if not wss.closed:
327
await wss.close()
163
# Handle plain text content from bucket
164
bucket_path = Path(get_bucket_dir(item["bucket_id"]))
165
for text_chunk in read_bucket(bucket_path):
166
if text_chunk.strip():
167
# Upload plain text as a text file
168
response_json = await page.evaluate(f'''
169
const formData = new FormData();
170
formData.append('file', new Blob(['{text_chunk.replace(chr(39), "\\'").replace(chr(10), "\\n").replace(chr(13), "\\r")}'], {{type: 'text/plain'}}), 'bucket_{item['bucket_id']}.txt');
171
fetch('https://copilot.microsoft.com/c/api/attachments', {{
172
method: 'POST',
173
headers: {{
174
"'x-useridentitytype': '{auth_result.useridentitytype}'," if auth_result.useridentitytype else ""
175
}},
176
body: formData
177
}}).then(r => r.json())
178
''')
179
data = response_json
180
uploaded_attachments.append({{"type": "document", "attachmentId": data.get("id")}})
181
debug.log(f"Copilot: Uploaded bucket text content: {item['bucket_id']}")
182
else:
183
debug.log(f"Copilot: No text content found in bucket: {item['bucket_id']}")
184
except Exception as e:
185
debug.log(f"Copilot: Failed to upload bucket item: {item}")
186
debug.error(e)
187
188
done = False
189
msg = None
190
image_prompt: str = None
191
last_msg = None
192
sources = {}
193
while not done:
194
try:
195
request_id, msg_txt = await asyncio.wait_for(queue.get(), 1 if done else timeout)
196
msg = json.loads(msg_txt)
197
except:
198
break
199
last_msg = msg
200
if msg.get("event") == "startMessage":
201
yield Conversation(msg.get("conversationId"))
202
elif msg.get("event") == "appendText":
203
yield msg.get("text")
204
elif msg.get("event") == "generatingImage":
205
image_prompt = msg.get("prompt")
206
elif msg.get("event") == "imageGenerated":
207
yield ImageResponse(msg.get("url"), image_prompt, {{"preview": msg.get("thumbnailUrl")}})
208
elif msg.get("event") == "done":
209
yield FinishReason("stop")
210
done = True
211
elif msg.get("event") == "suggestedFollowups":
212
yield SuggestedFollowups(msg.get("suggestions"))
213
break
214
elif msg.get("event") == "replaceText":
215
yield msg.get("text")
216
elif msg.get("event") == "titleUpdate":
217
yield TitleGeneration(msg.get("title"))
218
elif msg.get("event") == "citation":
219
sources[msg.get("url")] = msg
220
yield SourceLink(list(sources.keys()).index(msg.get("url")), msg.get("url"))
221
elif msg.get("event") == "partialImageGenerated":
222
mime_type = is_accepted_format(base64.b64decode(msg.get("content")[:12]))
223
yield ImagePreview(f"data:{mime_type};base64,{msg.get('content')}", image_prompt)
224
elif msg.get("event") == "chainOfThought":
225
yield Reasoning(msg.get("text"))
226
elif msg.get("event") == "error":
227
raise RuntimeError(f"Error: {msg}")
228
elif msg.get("event") not in ["received", "startMessage", "partCompleted", "connected"]:
229
debug.log(f"Copilot Message: {msg_txt[:100]}...")
230
if not done:
231
raise MissingAuthError(f"Invalid response: {last_msg}")
232
if sources:
233
yield Sources(sources.values())
328
234
329
235
async def get_access_token_and_cookies(url: str, proxy: str = None, needs_auth: bool = False):
330
236
browser, stop_browser = await get_nodriver(proxy=proxy)
@@ -16,7 +16,7 @@ def get_config_dir() -> Path:
16
16
17
17
DEFAULT_PORT = 1337
18
18
DEFAULT_TIMEOUT = 600
19
DEFAULT_STREAM_TIMEOUT = 15
19
DEFAULT_STREAM_TIMEOUT = 30
20
20
21
21
PACKAGE_NAME = "g4f"
22
22
CONFIG_DIR = get_config_dir() / PACKAGE_NAME