xiaolv commited on
Commit
fae4ad2
·
1 Parent(s): 1af85d7

Upload EdgeGPT.py

Browse files
Files changed (1) hide show
  1. EdgeGPT.py +880 -0
EdgeGPT.py ADDED
@@ -0,0 +1,880 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main.py
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import asyncio
8
+ import json
9
+ import os
10
+ import random
11
+ import re
12
+ import ssl
13
+ import sys
14
+ import uuid
15
+ from enum import Enum
16
+ from pathlib import Path
17
+ from typing import Generator
18
+ from typing import Literal
19
+ from typing import Optional
20
+ from typing import Union
21
+
22
+ import certifi
23
+ import httpx
24
+ import websockets.client as websockets
25
+ from BingImageCreator import ImageGenAsync
26
+ from prompt_toolkit import PromptSession
27
+ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
28
+ from prompt_toolkit.completion import WordCompleter
29
+ from prompt_toolkit.history import InMemoryHistory
30
+ from prompt_toolkit.key_binding import KeyBindings
31
+ from rich.live import Live
32
+ from rich.markdown import Markdown
33
+
34
+ DELIMITER = "\x1e"
35
+
36
+
37
+ # Generate random IP between range 13.104.0.0/14
38
+ FORWARDED_IP = (
39
+ f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
40
+ )
41
+
42
+ HEADERS = {
43
+ "accept": "application/json",
44
+ "accept-language": "en-US,en;q=0.9",
45
+ "content-type": "application/json",
46
+ "sec-ch-ua": '"Not_A Brand";v="99", "Microsoft Edge";v="110", "Chromium";v="110"',
47
+ "sec-ch-ua-arch": '"x86"',
48
+ "sec-ch-ua-bitness": '"64"',
49
+ "sec-ch-ua-full-version": '"109.0.1518.78"',
50
+ "sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
51
+ "sec-ch-ua-mobile": "?0",
52
+ "sec-ch-ua-model": "",
53
+ "sec-ch-ua-platform": '"Windows"',
54
+ "sec-ch-ua-platform-version": '"15.0.0"',
55
+ "sec-fetch-dest": "empty",
56
+ "sec-fetch-mode": "cors",
57
+ "sec-fetch-site": "same-origin",
58
+ "x-ms-client-request-id": str(uuid.uuid4()),
59
+ "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32",
60
+ "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx",
61
+ "Referrer-Policy": "origin-when-cross-origin",
62
+ "x-forwarded-for": FORWARDED_IP,
63
+ }
64
+
65
+ HEADERS_INIT_CONVER = {
66
+ "authority": "edgeservices.bing.com",
67
+ "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",
68
+ "accept-language": "en-US,en;q=0.9",
69
+ "cache-control": "max-age=0",
70
+ "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
71
+ "sec-ch-ua-arch": '"x86"',
72
+ "sec-ch-ua-bitness": '"64"',
73
+ "sec-ch-ua-full-version": '"110.0.1587.69"',
74
+ "sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
75
+ "sec-ch-ua-mobile": "?0",
76
+ "sec-ch-ua-model": '""',
77
+ "sec-ch-ua-platform": '"Windows"',
78
+ "sec-ch-ua-platform-version": '"15.0.0"',
79
+ "sec-fetch-dest": "document",
80
+ "sec-fetch-mode": "navigate",
81
+ "sec-fetch-site": "none",
82
+ "sec-fetch-user": "?1",
83
+ "upgrade-insecure-requests": "1",
84
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69",
85
+ "x-edge-shopping-flag": "1",
86
+ "x-forwarded-for": FORWARDED_IP,
87
+ }
88
+
89
+ ssl_context = ssl.create_default_context()
90
+ ssl_context.load_verify_locations(certifi.where())
91
+
92
+
93
+ class NotAllowedToAccess(Exception):
94
+ pass
95
+
96
+
97
+ class ConversationStyle(Enum):
98
+ creative = [
99
+ "nlu_direct_response_filter",
100
+ "deepleo",
101
+ "disable_emoji_spoken_text",
102
+ "responsible_ai_policy_235",
103
+ "enablemm",
104
+ "h3imaginative",
105
+ "travelansgnd",
106
+ "dv3sugg",
107
+ "clgalileo",
108
+ "gencontentv3",
109
+ "dv3sugg",
110
+ "responseos",
111
+ "e2ecachewrite",
112
+ "cachewriteext",
113
+ "nodlcpcwrite",
114
+ "travelansgnd",
115
+ "nojbfedge",
116
+ ]
117
+ balanced = [
118
+ "nlu_direct_response_filter",
119
+ "deepleo",
120
+ "disable_emoji_spoken_text",
121
+ "responsible_ai_policy_235",
122
+ "enablemm",
123
+ "galileo",
124
+ "dv3sugg",
125
+ "responseos",
126
+ "e2ecachewrite",
127
+ "cachewriteext",
128
+ "nodlcpcwrite",
129
+ "travelansgnd",
130
+ "nojbfedge",
131
+ ]
132
+ precise = [
133
+ "nlu_direct_response_filter",
134
+ "deepleo",
135
+ "disable_emoji_spoken_text",
136
+ "responsible_ai_policy_235",
137
+ "enablemm",
138
+ "galileo",
139
+ "dv3sugg",
140
+ "responseos",
141
+ "e2ecachewrite",
142
+ "cachewriteext",
143
+ "nodlcpcwrite",
144
+ "travelansgnd",
145
+ "h3precise",
146
+ "clgalileo",
147
+ "nojbfedge",
148
+ ]
149
+
150
+
151
+ CONVERSATION_STYLE_TYPE = Optional[
152
+ Union[ConversationStyle, Literal["creative", "balanced", "precise"]]
153
+ ]
154
+
155
+
156
+ def _append_identifier(msg: dict) -> str:
157
+ """
158
+ Appends special character to end of message to identify end of message
159
+ """
160
+ # Convert dict to json string
161
+ return json.dumps(msg, ensure_ascii=False) + DELIMITER
162
+
163
+
164
+ def _get_ran_hex(length: int = 32) -> str:
165
+ """
166
+ Returns random hex string
167
+ """
168
+ return "".join(random.choice("0123456789abcdef") for _ in range(length))
169
+
170
+
171
+ class _ChatHubRequest:
172
+ """
173
+ Request object for ChatHub
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ conversation_signature: str,
179
+ client_id: str,
180
+ conversation_id: str,
181
+ invocation_id: int = 0,
182
+ ) -> None:
183
+ self.struct: dict = {}
184
+
185
+ self.client_id: str = client_id
186
+ self.conversation_id: str = conversation_id
187
+ self.conversation_signature: str = conversation_signature
188
+ self.invocation_id: int = invocation_id
189
+
190
+ def update(
191
+ self,
192
+ prompt: str,
193
+ conversation_style: CONVERSATION_STYLE_TYPE,
194
+ options: list | None = None,
195
+ webpage_context: str | None = None,
196
+ search_result: bool = False,
197
+ ) -> None:
198
+ """
199
+ Updates request object
200
+ """
201
+ if options is None:
202
+ options = [
203
+ "deepleo",
204
+ "enable_debug_commands",
205
+ "disable_emoji_spoken_text",
206
+ "enablemm",
207
+ ]
208
+ if conversation_style:
209
+ if not isinstance(conversation_style, ConversationStyle):
210
+ conversation_style = getattr(ConversationStyle, conversation_style)
211
+ options = conversation_style.value
212
+ self.struct = {
213
+ "arguments": [
214
+ {
215
+ "source": "cib",
216
+ "optionsSets": options,
217
+ "allowedMessageTypes": [
218
+ "Chat",
219
+ "Disengaged",
220
+ "AdsQuery",
221
+ "SemanticSerp",
222
+ "GenerateContentQuery",
223
+ "SearchQuery",
224
+ ],
225
+ "sliceIds": [
226
+ "chk1cf",
227
+ "nopreloadsscf",
228
+ "winlongmsg2tf",
229
+ "perfimpcomb",
230
+ "sugdivdis",
231
+ "sydnoinputt",
232
+ "wpcssopt",
233
+ "wintone2tf",
234
+ "0404sydicnbs0",
235
+ "405suggbs0",
236
+ "scctl",
237
+ "330uaugs0",
238
+ "0329resp",
239
+ "udscahrfon",
240
+ "udstrblm5",
241
+ "404e2ewrt",
242
+ "408nodedups0",
243
+ "403tvlansgnd",
244
+ ],
245
+ "traceId": _get_ran_hex(32),
246
+ "isStartOfSession": self.invocation_id == 0,
247
+ "message": {
248
+ "author": "user",
249
+ "inputMethod": "Keyboard",
250
+ "text": prompt,
251
+ "messageType": "Chat",
252
+ },
253
+ "conversationSignature": self.conversation_signature,
254
+ "participant": {
255
+ "id": self.client_id,
256
+ },
257
+ "conversationId": self.conversation_id,
258
+ },
259
+ ],
260
+ "invocationId": str(self.invocation_id),
261
+ "target": "chat",
262
+ "type": 4,
263
+ }
264
+ if search_result:
265
+ have_search_result = [
266
+ "InternalSearchQuery",
267
+ "InternalSearchResult",
268
+ "InternalLoaderMessage",
269
+ "RenderCardRequest",
270
+ ]
271
+ self.struct["arguments"][0]["allowedMessageTypes"] += have_search_result
272
+ if webpage_context:
273
+ self.struct["arguments"][0]["previousMessages"] = [
274
+ {
275
+ "author": "user",
276
+ "description": webpage_context,
277
+ "contextType": "WebPage",
278
+ "messageType": "Context",
279
+ "messageId": "discover-web--page-ping-mriduna-----",
280
+ },
281
+ ]
282
+ self.invocation_id += 1
283
+
284
+
285
+ class _Conversation:
286
+ """
287
+ Conversation API
288
+ """
289
+
290
+ def __init__(
291
+ self,
292
+ cookies: dict | None = None,
293
+ proxy: str | None = None,
294
+ async_mode: bool = False,
295
+ ) -> None:
296
+ if async_mode:
297
+ return
298
+ self.struct: dict = {
299
+ "conversationId": None,
300
+ "clientId": None,
301
+ "conversationSignature": None,
302
+ "result": {"value": "Success", "message": None},
303
+ }
304
+ self.proxy = proxy
305
+ proxy = (
306
+ proxy
307
+ or os.environ.get("all_proxy")
308
+ or os.environ.get("ALL_PROXY")
309
+ or os.environ.get("https_proxy")
310
+ or os.environ.get("HTTPS_PROXY")
311
+ or None
312
+ )
313
+ if proxy is not None and proxy.startswith("socks5h://"):
314
+ proxy = "socks5://" + proxy[len("socks5h://") :]
315
+ self.session = httpx.Client(
316
+ proxies=proxy,
317
+ timeout=30,
318
+ headers=HEADERS_INIT_CONVER,
319
+ )
320
+ for cookie in cookies:
321
+ self.session.cookies.set(cookie["name"], cookie["value"])
322
+
323
+ # Send GET request
324
+ response = self.session.get(
325
+ url=os.environ.get("BING_PROXY_URL")
326
+ or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
327
+ )
328
+ if response.status_code != 200:
329
+ response = self.session.get(
330
+ "https://edge.churchless.tech/edgesvc/turing/conversation/create",
331
+ )
332
+ if response.status_code != 200:
333
+ print(f"Status code: {response.status_code}")
334
+ print(response.text)
335
+ print(response.url)
336
+ raise Exception("Authentication failed")
337
+ try:
338
+ self.struct = response.json()
339
+ except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
340
+ raise Exception(
341
+ "Authentication failed. You have not been accepted into the beta.",
342
+ ) from exc
343
+ if self.struct["result"]["value"] == "UnauthorizedRequest":
344
+ raise NotAllowedToAccess(self.struct["result"]["message"])
345
+
346
+ @staticmethod
347
+ async def create(
348
+ cookies: dict,
349
+ proxy: str | None = None,
350
+ ) -> _Conversation:
351
+ self = _Conversation(async_mode=True)
352
+ self.struct = {
353
+ "conversationId": None,
354
+ "clientId": None,
355
+ "conversationSignature": None,
356
+ "result": {"value": "Success", "message": None},
357
+ }
358
+ self.proxy = proxy
359
+ proxy = (
360
+ proxy
361
+ or os.environ.get("all_proxy")
362
+ or os.environ.get("ALL_PROXY")
363
+ or os.environ.get("https_proxy")
364
+ or os.environ.get("HTTPS_PROXY")
365
+ or None
366
+ )
367
+ if proxy is not None and proxy.startswith("socks5h://"):
368
+ proxy = "socks5://" + proxy[len("socks5h://") :]
369
+ transport = httpx.AsyncHTTPTransport(retries=10)
370
+ async with httpx.AsyncClient(
371
+ proxies=proxy,
372
+ timeout=30,
373
+ headers=HEADERS_INIT_CONVER,
374
+ transport=transport,
375
+ ) as client:
376
+ for cookie in cookies:
377
+ client.cookies.set(cookie["name"], cookie["value"])
378
+
379
+ # Send GET request
380
+ response = await client.get(
381
+ url=os.environ.get("BING_PROXY_URL")
382
+ or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
383
+ )
384
+ if response.status_code != 200:
385
+ response = await client.get(
386
+ "https://edge.churchless.tech/edgesvc/turing/conversation/create",
387
+ )
388
+ if response.status_code != 200:
389
+ print(f"Status code: {response.status_code}")
390
+ print(response.text)
391
+ print(response.url)
392
+ raise Exception("Authentication failed")
393
+ try:
394
+ self.struct = response.json()
395
+ except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
396
+ raise Exception(
397
+ "Authentication failed. You have not been accepted into the beta.",
398
+ ) from exc
399
+ if self.struct["result"]["value"] == "UnauthorizedRequest":
400
+ raise NotAllowedToAccess(self.struct["result"]["message"])
401
+ return self
402
+
403
+
404
+ class _ChatHub:
405
+ """
406
+ Chat API
407
+ """
408
+
409
+ def __init__(self, conversation: _Conversation) -> None:
410
+ self.wss: websockets.WebSocketClientProtocol | None = None
411
+ self.request: _ChatHubRequest
412
+ self.loop: bool
413
+ self.task: asyncio.Task
414
+ self.request = _ChatHubRequest(
415
+ conversation_signature=conversation.struct["conversationSignature"],
416
+ client_id=conversation.struct["clientId"],
417
+ conversation_id=conversation.struct["conversationId"],
418
+ )
419
+
420
+ async def ask_stream(
421
+ self,
422
+ prompt: str,
423
+ wss_link: str,
424
+ cookies: str,
425
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
426
+ raw: bool = False,
427
+ options: dict = None,
428
+ webpage_context: str | None = None,
429
+ search_result: bool = False,
430
+ ) -> Generator[str, None, None]:
431
+ """
432
+ Ask a question to the bot
433
+ """
434
+ if self.wss and not self.wss.closed:
435
+ await self.wss.close()
436
+ # Check if websocket is closed
437
+ self.wss = await websockets.connect(
438
+ wss_link,
439
+ extra_headers=HEADERS,
440
+ max_size=None,
441
+ ssl=ssl_context,
442
+ )
443
+ await self._initial_handshake()
444
+ if self.request.invocation_id == 0:
445
+ # Construct a ChatHub request
446
+ self.request.update(
447
+ prompt=prompt,
448
+ conversation_style=conversation_style,
449
+ options=options,
450
+ webpage_context=webpage_context,
451
+ search_result=search_result,
452
+ )
453
+ else:
454
+ async with httpx.AsyncClient() as client:
455
+ response = await client.post(
456
+ "https://sydney.bing.com/sydney/UpdateConversation/",
457
+ json={
458
+ "messages": [
459
+ {
460
+ "author": "user",
461
+ "description": webpage_context,
462
+ "contextType": "WebPage",
463
+ "messageType": "Context",
464
+ },
465
+ ],
466
+ "conversationId": self.request.conversation_id,
467
+ "source": "cib",
468
+ "traceId": _get_ran_hex(32),
469
+ "participant": {"id": self.request.client_id},
470
+ "conversationSignature": self.request.conversation_signature,
471
+ },
472
+ )
473
+ if response.status_code != 200:
474
+ print(f"Status code: {response.status_code}")
475
+ print(response.text)
476
+ print(response.url)
477
+ raise Exception("Update web page context failed")
478
+ # Construct a ChatHub request
479
+ self.request.update(
480
+ prompt=prompt,
481
+ conversation_style=conversation_style,
482
+ options=options,
483
+ )
484
+ # Send request
485
+ await self.wss.send(_append_identifier(self.request.struct))
486
+ final = False
487
+ draw = False
488
+ resp_txt = ""
489
+ result_text = ""
490
+ resp_txt_no_link = ""
491
+ while not final:
492
+ objects = str(await self.wss.recv()).split(DELIMITER)
493
+ for obj in objects:
494
+ if obj is None or not obj:
495
+ continue
496
+ response = json.loads(obj)
497
+ if response.get("type") != 2 and raw:
498
+ yield False, response
499
+ elif response.get("type") == 1 and response["arguments"][0].get(
500
+ "messages",
501
+ ):
502
+ if not draw:
503
+ if (
504
+ response["arguments"][0]["messages"][0].get("messageType")
505
+ == "GenerateContentQuery"
506
+ ):
507
+ for item in cookies:
508
+ if item["name"] == "_U":
509
+ U = item["value"]
510
+ async with ImageGenAsync(U, True) as image_generator:
511
+ images = await image_generator.get_images(
512
+ response["arguments"][0]["messages"][0]["text"],
513
+ )
514
+ for i, image in enumerate(images):
515
+ resp_txt = resp_txt + f"\n![image{i}]({image})"
516
+ draw = True
517
+ if (
518
+ response["arguments"][0]["messages"][0]["contentOrigin"]
519
+ != "Apology"
520
+ ) and not draw:
521
+ resp_txt = result_text + response["arguments"][0][
522
+ "messages"
523
+ ][0]["adaptiveCards"][0]["body"][0].get("text", "")
524
+ resp_txt_no_link = result_text + response["arguments"][0][
525
+ "messages"
526
+ ][0].get("text", "")
527
+ if response["arguments"][0]["messages"][0].get(
528
+ "messageType",
529
+ ):
530
+ resp_txt = (
531
+ resp_txt
532
+ + response["arguments"][0]["messages"][0][
533
+ "adaptiveCards"
534
+ ][0]["body"][0]["inlines"][0].get("text")
535
+ + "\n"
536
+ )
537
+ result_text = (
538
+ result_text
539
+ + response["arguments"][0]["messages"][0][
540
+ "adaptiveCards"
541
+ ][0]["body"][0]["inlines"][0].get("text")
542
+ + "\n"
543
+ )
544
+ yield False, resp_txt
545
+
546
+ elif response.get("type") == 2:
547
+ if draw:
548
+ cache = response["item"]["messages"][1]["adaptiveCards"][0][
549
+ "body"
550
+ ][0]["text"]
551
+ response["item"]["messages"][1]["adaptiveCards"][0]["body"][0][
552
+ "text"
553
+ ] = (cache + resp_txt)
554
+ if (
555
+ response["item"]["messages"][-1]["contentOrigin"] == "Apology"
556
+ and resp_txt
557
+ ):
558
+ response["item"]["messages"][-1]["text"] = resp_txt_no_link
559
+ response["item"]["messages"][-1]["adaptiveCards"][0]["body"][0][
560
+ "text"
561
+ ] = resp_txt
562
+ print(
563
+ "Preserved the message from being deleted",
564
+ file=sys.stderr,
565
+ )
566
+ final = True
567
+ yield True, response
568
+
569
+ async def _initial_handshake(self) -> None:
570
+ await self.wss.send(_append_identifier({"protocol": "json", "version": 1}))
571
+ await self.wss.recv()
572
+
573
+ async def close(self) -> None:
574
+ """
575
+ Close the connection
576
+ """
577
+ if self.wss and not self.wss.closed:
578
+ await self.wss.close()
579
+
580
+
581
+ class Chatbot:
582
+ """
583
+ Combines everything to make it seamless
584
+ """
585
+
586
+ def __init__(
587
+ self,
588
+ cookies: dict = None,
589
+ proxy: str | None = None,
590
+ cookie_path: str = None,
591
+ ) -> None:
592
+ if cookies is None:
593
+ cookies = {}
594
+ if cookie_path is not None:
595
+ try:
596
+ with open(cookie_path, encoding="utf-8") as f:
597
+ self.cookies = json.load(f)
598
+ except FileNotFoundError as exc:
599
+ raise FileNotFoundError("Cookie file not found") from exc
600
+ else:
601
+ self.cookies = cookies
602
+ self.proxy: str | None = proxy
603
+ self.chat_hub: _ChatHub = _ChatHub(
604
+ _Conversation(self.cookies, self.proxy),
605
+ )
606
+
607
+ @staticmethod
608
+ async def create(
609
+ cookies: dict = None,
610
+ proxy: str | None = None,
611
+ cookie_path: str = None,
612
+ ):
613
+ self = Chatbot.__new__(Chatbot)
614
+ if cookies is None:
615
+ cookies = {}
616
+ if cookie_path is not None:
617
+ try:
618
+ with open(cookie_path, encoding="utf-8") as f:
619
+ self.cookies = json.load(f)
620
+ except FileNotFoundError as exc:
621
+ raise FileNotFoundError("Cookie file not found") from exc
622
+ else:
623
+ self.cookies = cookies
624
+ self.proxy = proxy
625
+ self.chat_hub = _ChatHub(
626
+ await _Conversation.create(self.cookies, self.proxy),
627
+ )
628
+ return self
629
+
630
+ async def ask(
631
+ self,
632
+ prompt: str,
633
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
634
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
635
+ options: dict = None,
636
+ webpage_context: str | None = None,
637
+ search_result: bool = False,
638
+ ) -> dict:
639
+ """
640
+ Ask a question to the bot
641
+ """
642
+ async for final, response in self.chat_hub.ask_stream(
643
+ prompt=prompt,
644
+ conversation_style=conversation_style,
645
+ wss_link=wss_link,
646
+ options=options,
647
+ cookies=self.cookies,
648
+ webpage_context=webpage_context,
649
+ search_result=search_result,
650
+ ):
651
+ if final:
652
+ return response
653
+ await self.chat_hub.wss.close()
654
+ return {}
655
+
656
+ async def ask_stream(
657
+ self,
658
+ prompt: str,
659
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
660
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
661
+ raw: bool = False,
662
+ options: dict = None,
663
+ webpage_context: str | None = None,
664
+ search_result: bool = False,
665
+ ) -> Generator[str, None, None]:
666
+ """
667
+ Ask a question to the bot
668
+ """
669
+ async for response in self.chat_hub.ask_stream(
670
+ prompt=prompt,
671
+ conversation_style=conversation_style,
672
+ wss_link=wss_link,
673
+ raw=raw,
674
+ options=options,
675
+ cookies=self.cookies,
676
+ webpage_context=webpage_context,
677
+ search_result=search_result,
678
+ ):
679
+ yield response
680
+
681
+ async def close(self) -> None:
682
+ """
683
+ Close the connection
684
+ """
685
+ await self.chat_hub.close()
686
+
687
+ async def reset(self) -> None:
688
+ """
689
+ Reset the conversation
690
+ """
691
+ await self.close()
692
+ self.chat_hub = _ChatHub(
693
+ await _Conversation.create(self.cookies, self.proxy),
694
+ )
695
+
696
+
697
+ async def _get_input_async(
698
+ session: PromptSession = None,
699
+ completer: WordCompleter = None,
700
+ ) -> str:
701
+ """
702
+ Multiline input function.
703
+ """
704
+ return await session.prompt_async(
705
+ completer=completer,
706
+ multiline=True,
707
+ auto_suggest=AutoSuggestFromHistory(),
708
+ )
709
+
710
+
711
+ def _create_session() -> PromptSession:
712
+ kb = KeyBindings()
713
+
714
+ @kb.add("enter")
715
+ def _(event):
716
+ buffer_text = event.current_buffer.text
717
+ if buffer_text.startswith("!"):
718
+ event.current_buffer.validate_and_handle()
719
+ else:
720
+ event.current_buffer.insert_text("\n")
721
+
722
+ @kb.add("escape")
723
+ def _(event):
724
+ if event.current_buffer.complete_state:
725
+ # event.current_buffer.cancel_completion()
726
+ event.current_buffer.text = ""
727
+
728
+ return PromptSession(key_bindings=kb, history=InMemoryHistory())
729
+
730
+
731
+ def _create_completer(commands: list, pattern_str: str = "$"):
732
+ return WordCompleter(words=commands, pattern=re.compile(pattern_str))
733
+
734
+
735
+ async def async_main(args: argparse.Namespace) -> None:
736
+ """
737
+ Main function
738
+ """
739
+ print("Initializing...")
740
+ print("Enter `alt+enter` or `escape+enter` to send a message")
741
+ bot = await Chatbot.create(proxy=args.proxy, cookies=args.cookies)
742
+ session = _create_session()
743
+ completer = _create_completer(["!help", "!exit", "!reset"])
744
+ initial_prompt = args.prompt
745
+
746
+ while True:
747
+ print("\nYou:")
748
+ if initial_prompt:
749
+ question = initial_prompt
750
+ print(question)
751
+ initial_prompt = None
752
+ else:
753
+ question = (
754
+ input()
755
+ if args.enter_once
756
+ else await _get_input_async(session=session, completer=completer)
757
+ )
758
+ print()
759
+ if question == "!exit":
760
+ break
761
+ if question == "!help":
762
+ print(
763
+ """
764
+ !help - Show this help message
765
+ !exit - Exit the program
766
+ !reset - Reset the conversation
767
+ """,
768
+ )
769
+ continue
770
+ if question == "!reset":
771
+ await bot.reset()
772
+ continue
773
+ print("Bot:")
774
+ if args.no_stream:
775
+ print(
776
+ (
777
+ await bot.ask(
778
+ prompt=question,
779
+ conversation_style=args.style,
780
+ wss_link=args.wss_link,
781
+ )
782
+ )["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"],
783
+ )
784
+ else:
785
+ wrote = 0
786
+ if args.rich:
787
+ md = Markdown("")
788
+ with Live(md, auto_refresh=False) as live:
789
+ async for final, response in bot.ask_stream(
790
+ prompt=question,
791
+ conversation_style=args.style,
792
+ wss_link=args.wss_link,
793
+ ):
794
+ if not final:
795
+ if wrote > len(response):
796
+ print(md)
797
+ print(Markdown("***Bing revoked the response.***"))
798
+ wrote = len(response)
799
+ md = Markdown(response)
800
+ live.update(md, refresh=True)
801
+ else:
802
+ async for final, response in bot.ask_stream(
803
+ prompt=question,
804
+ conversation_style=args.style,
805
+ wss_link=args.wss_link,
806
+ ):
807
+ if not final:
808
+ if not wrote:
809
+ print(response, end="", flush=True)
810
+ else:
811
+ print(response[wrote:], end="", flush=True)
812
+ wrote = len(response)
813
+ print()
814
+ await bot.close()
815
+
816
+
817
+ def main() -> None:
818
+ print(
819
+ """
820
+ EdgeGPT - A demo of reverse engineering the Bing GPT chatbot
821
+ Repo: github.com/acheong08/EdgeGPT
822
+ By: Antonio Cheong
823
+
824
+ !help for help
825
+
826
+ Type !exit to exit
827
+ """,
828
+ )
829
+ parser = argparse.ArgumentParser()
830
+ parser.add_argument("--enter-once", action="store_true")
831
+ parser.add_argument("--no-stream", action="store_true")
832
+ parser.add_argument("--rich", action="store_true")
833
+ parser.add_argument(
834
+ "--proxy",
835
+ help="Proxy URL (e.g. socks5://127.0.0.1:1080)",
836
+ type=str,
837
+ )
838
+ parser.add_argument(
839
+ "--wss-link",
840
+ help="WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)",
841
+ type=str,
842
+ default="wss://sydney.bing.com/sydney/ChatHub",
843
+ )
844
+ parser.add_argument(
845
+ "--style",
846
+ choices=["creative", "balanced", "precise"],
847
+ default="balanced",
848
+ )
849
+ parser.add_argument(
850
+ "--cookie-file",
851
+ type=str,
852
+ default=os.environ.get("COOKIE_FILE", ""),
853
+ required=False,
854
+ help="Cookie file used for authentication (defaults to COOKIE_FILE environment variable)",
855
+ )
856
+ parser.add_argument(
857
+ "--prompt",
858
+ type=str,
859
+ default="",
860
+ required=False,
861
+ help="prompt to start with",
862
+ )
863
+ args = parser.parse_args()
864
+ if not args.cookie_file:
865
+ parser.print_help()
866
+ parser.exit(
867
+ 1,
868
+ "ERROR: use --cookie-file or set the COOKIE_FILE environment variable",
869
+ )
870
+ try:
871
+ args.cookies = json.loads(Path(args.cookie_file).read_text(encoding="utf-8"))
872
+ except OSError as exc:
873
+ print(f"Could not open cookie file: {exc}", file=sys.stderr)
874
+ sys.exit(1)
875
+
876
+ asyncio.run(async_main(args))
877
+
878
+
879
+ if __name__ == "__main__":
880
+ main()