Not-Grim-Refer commited on
Commit
9ef2570
1 Parent(s): c491e45

Delete EdgeGPT.py

Browse files
Files changed (1) hide show
  1. EdgeGPT.py +0 -1293
EdgeGPT.py DELETED
@@ -1,1293 +0,0 @@
1
- """
2
- Main.py
3
- """
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import asyncio
8
- import aiofiles
9
- import json
10
- import os
11
- import random
12
- import re
13
- import ssl
14
- import sys
15
- import time
16
- import uuid
17
- from enum import Enum
18
- from pathlib import Path
19
- from typing import Generator
20
-
21
- try:
22
- from typing import Literal
23
- except ImportError:
24
- from typing_extensions import Literal
25
- from typing import Optional
26
- from typing import Union
27
-
28
- import aiohttp
29
- import certifi
30
- import httpx
31
- from BingImageCreator import ImageGen
32
- from BingImageCreator import ImageGenAsync
33
- from prompt_toolkit import PromptSession
34
- from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
35
- from prompt_toolkit.completion import WordCompleter
36
- from prompt_toolkit.history import InMemoryHistory
37
- from prompt_toolkit.key_binding import KeyBindings
38
- from rich.live import Live
39
- from rich.markdown import Markdown
40
-
41
- DELIMITER = "\x1e"
42
-
43
-
44
- # Generate random IP between range 13.104.0.0/14
45
- FORWARDED_IP = (
46
- f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
47
- )
48
-
49
- HEADERS = {
50
- "accept": "application/json",
51
- "accept-language": "en-US,en;q=0.9",
52
- "content-type": "application/json",
53
- "sec-ch-ua": '"Not_A Brand";v="99", "Microsoft Edge";v="110", "Chromium";v="110"',
54
- "sec-ch-ua-arch": '"x86"',
55
- "sec-ch-ua-bitness": '"64"',
56
- "sec-ch-ua-full-version": '"109.0.1518.78"',
57
- "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"',
58
- "sec-ch-ua-mobile": "?0",
59
- "sec-ch-ua-model": "",
60
- "sec-ch-ua-platform": '"Windows"',
61
- "sec-ch-ua-platform-version": '"15.0.0"',
62
- "sec-fetch-dest": "empty",
63
- "sec-fetch-mode": "cors",
64
- "sec-fetch-site": "same-origin",
65
- "x-ms-client-request-id": str(uuid.uuid4()),
66
- "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32",
67
- "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx",
68
- "Referrer-Policy": "origin-when-cross-origin",
69
- "x-forwarded-for": FORWARDED_IP,
70
- }
71
-
72
- HEADERS_INIT_CONVER = {
73
- "authority": "edgeservices.bing.com",
74
- "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",
75
- "accept-language": "en-US,en;q=0.9",
76
- "cache-control": "max-age=0",
77
- "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
78
- "sec-ch-ua-arch": '"x86"',
79
- "sec-ch-ua-bitness": '"64"',
80
- "sec-ch-ua-full-version": '"110.0.1587.69"',
81
- "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"',
82
- "sec-ch-ua-mobile": "?0",
83
- "sec-ch-ua-model": '""',
84
- "sec-ch-ua-platform": '"Windows"',
85
- "sec-ch-ua-platform-version": '"15.0.0"',
86
- "sec-fetch-dest": "document",
87
- "sec-fetch-mode": "navigate",
88
- "sec-fetch-site": "none",
89
- "sec-fetch-user": "?1",
90
- "upgrade-insecure-requests": "1",
91
- "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",
92
- "x-edge-shopping-flag": "1",
93
- "x-forwarded-for": FORWARDED_IP,
94
- }
95
-
96
- ssl_context = ssl.create_default_context()
97
- ssl_context.load_verify_locations(certifi.where())
98
-
99
-
100
- class NotAllowedToAccess(Exception):
101
- pass
102
-
103
-
104
- class LocationHint(Enum):
105
- USA = {
106
- "locale": "en-US",
107
- "LocationHint": [
108
- {
109
- "country": "United States",
110
- "state": "California",
111
- "city": "Los Angeles",
112
- "timezoneoffset": 8,
113
- "countryConfidence": 8,
114
- "Center": {
115
- "Latitude": 34.0536909,
116
- "Longitude": -118.242766,
117
- },
118
- "RegionType": 2,
119
- "SourceType": 1,
120
- }
121
- ],
122
- }
123
- CHINA = {
124
- "locale": "zh-CN",
125
- "LocationHint": [
126
- {
127
- "country": "China",
128
- "state": "",
129
- "city": "Beijing",
130
- "timezoneoffset": 8,
131
- "countryConfidence": 8,
132
- "Center": {
133
- "Latitude": 39.9042,
134
- "Longitude": 116.4074,
135
- },
136
- "RegionType": 2,
137
- "SourceType": 1,
138
- }
139
- ],
140
- }
141
- EU = {
142
- "locale": "en-IE",
143
- "LocationHint": [
144
- {
145
- "country": "Norway",
146
- "state": "",
147
- "city": "Oslo",
148
- "timezoneoffset": 1,
149
- "countryConfidence": 8,
150
- "Center": {
151
- "Latitude": 59.9139,
152
- "Longitude": 10.7522,
153
- },
154
- "RegionType": 2,
155
- "SourceType": 1,
156
- }
157
- ],
158
- }
159
- UK = {
160
- "locale": "en-GB",
161
- "LocationHint": [
162
- {
163
- "country": "United Kingdom",
164
- "state": "",
165
- "city": "London",
166
- "timezoneoffset": 0,
167
- "countryConfidence": 8,
168
- "Center": {
169
- "Latitude": 51.5074,
170
- "Longitude": -0.1278,
171
- },
172
- "RegionType": 2,
173
- "SourceType": 1,
174
- },
175
- ],
176
- }
177
-
178
-
179
- LOCATION_HINT_TYPES = Optional[Union[LocationHint, Literal["USA", "CHINA", "EU", "UK"]]]
180
-
181
-
182
- def get_location_hint_from_locale(locale: str) -> dict | None:
183
- locale = locale.lower()
184
- if locale == "en-us":
185
- return LocationHint.USA.value
186
- elif locale == "zh-cn":
187
- return LocationHint.CHINA.value
188
- elif locale == "en-gb":
189
- return LocationHint.UK.value
190
- elif locale == "en-ie":
191
- return LocationHint.EU.value
192
- else:
193
- return None
194
-
195
-
196
- class ConversationStyle(Enum):
197
- creative = [
198
- "nlu_direct_response_filter",
199
- "deepleo",
200
- "disable_emoji_spoken_text",
201
- "responsible_ai_policy_235",
202
- "enablemm",
203
- "h3imaginative",
204
- "cachewriteext",
205
- "e2ecachewrite",
206
- "nodlcpcwrite",
207
- "nointernalsugg",
208
- "saharasugg",
209
- "enablenewsfc",
210
- "dv3sugg",
211
- "clgalileo",
212
- "gencontentv3",
213
- "nojbfedge",
214
- ]
215
- balanced = [
216
- "nlu_direct_response_filter",
217
- "deepleo",
218
- "disable_emoji_spoken_text",
219
- "responsible_ai_policy_235",
220
- "enablemm",
221
- "harmonyv3",
222
- "cachewriteext",
223
- "e2ecachewrite",
224
- "nodlcpcwrite",
225
- "nointernalsugg",
226
- "saharasugg",
227
- "enablenewsfc",
228
- "dv3sugg",
229
- "nojbfedge",
230
- ]
231
- precise = [
232
- "nlu_direct_response_filter",
233
- "deepleo",
234
- "disable_emoji_spoken_text",
235
- "responsible_ai_policy_235",
236
- "enablemm",
237
- "h3precise",
238
- "cachewriteext",
239
- "e2ecachewrite",
240
- "nodlcpcwrite",
241
- "nointernalsugg",
242
- "saharasugg",
243
- "enablenewsfc",
244
- "dv3sugg",
245
- "clgalileo",
246
- "gencontentv3",
247
- "nojbfedge",
248
- ]
249
-
250
-
251
- CONVERSATION_STYLE_TYPE = Optional[
252
- Union[ConversationStyle, Literal["creative", "balanced", "precise"]]
253
- ]
254
-
255
-
256
- def _append_identifier(msg: dict) -> str:
257
- """
258
- Appends special character to end of message to identify end of message
259
- """
260
- # Convert dict to json string
261
- return json.dumps(msg, ensure_ascii=False) + DELIMITER
262
-
263
-
264
- def _get_ran_hex(length: int = 32) -> str:
265
- """
266
- Returns random hex string
267
- """
268
- return "".join(random.choice("0123456789abcdef") for _ in range(length))
269
-
270
-
271
- class _ChatHubRequest:
272
- """
273
- Request object for ChatHub
274
- """
275
-
276
- def __init__(
277
- self,
278
- conversation_signature: str,
279
- client_id: str,
280
- conversation_id: str,
281
- invocation_id: int = 0,
282
- ) -> None:
283
- self.struct: dict = {}
284
-
285
- self.client_id: str = client_id
286
- self.conversation_id: str = conversation_id
287
- self.conversation_signature: str = conversation_signature
288
- self.invocation_id: int = invocation_id
289
-
290
- def update(
291
- self,
292
- prompt: str,
293
- conversation_style: CONVERSATION_STYLE_TYPE,
294
- options: list | None = None,
295
- webpage_context: str | None = None,
296
- search_result: bool = False,
297
- locale: str = "en-US",
298
- ) -> None:
299
- """
300
- Updates request object
301
- """
302
- if options is None:
303
- options = [
304
- "deepleo",
305
- "enable_debug_commands",
306
- "disable_emoji_spoken_text",
307
- "enablemm",
308
- ]
309
- if conversation_style:
310
- if not isinstance(conversation_style, ConversationStyle):
311
- conversation_style = getattr(ConversationStyle, conversation_style)
312
- options = conversation_style.value
313
- self.struct = {
314
- "arguments": [
315
- {
316
- "source": "cib",
317
- "optionsSets": options,
318
- "allowedMessageTypes": [
319
- "Chat",
320
- "Disengaged",
321
- "AdsQuery",
322
- "SemanticSerp",
323
- "GenerateContentQuery",
324
- "SearchQuery",
325
- "ActionRequest",
326
- "Context",
327
- "Progress",
328
- "AdsQuery",
329
- "SemanticSerp",
330
- ],
331
- "sliceIds": [
332
- "winmuid3tf",
333
- "osbsdusgreccf",
334
- "ttstmout",
335
- "crchatrev",
336
- "winlongmsgtf",
337
- "ctrlworkpay",
338
- "norespwtf",
339
- "tempcacheread",
340
- "temptacache",
341
- "505scss0",
342
- "508jbcars0",
343
- "515enbotdets0",
344
- "5082tsports",
345
- "515vaoprvs",
346
- "424dagslnv1s0",
347
- "kcimgattcf",
348
- "427startpms0",
349
- ],
350
- "traceId": _get_ran_hex(32),
351
- "isStartOfSession": self.invocation_id == 0,
352
- "message": {
353
- "locale": locale,
354
- "market": locale,
355
- "region": locale[-2:], # en-US -> US
356
- "locationHints": [get_location_hint_from_locale(locale)],
357
- "author": "user",
358
- "inputMethod": "Keyboard",
359
- "text": prompt,
360
- "messageType": "Chat",
361
- },
362
- "conversationSignature": self.conversation_signature,
363
- "participant": {
364
- "id": self.client_id,
365
- },
366
- "conversationId": self.conversation_id,
367
- },
368
- ],
369
- "invocationId": str(self.invocation_id),
370
- "target": "chat",
371
- "type": 4,
372
- }
373
- if search_result:
374
- have_search_result = [
375
- "InternalSearchQuery",
376
- "InternalSearchResult",
377
- "InternalLoaderMessage",
378
- "RenderCardRequest",
379
- ]
380
- self.struct["arguments"][0]["allowedMessageTypes"] += have_search_result
381
- if webpage_context:
382
- self.struct["arguments"][0]["previousMessages"] = [
383
- {
384
- "author": "user",
385
- "description": webpage_context,
386
- "contextType": "WebPage",
387
- "messageType": "Context",
388
- "messageId": "discover-web--page-ping-mriduna-----",
389
- },
390
- ]
391
- self.invocation_id += 1
392
-
393
-
394
- class _Conversation:
395
- """
396
- Conversation API
397
- """
398
-
399
- def __init__(
400
- self,
401
- proxy: str | None = None,
402
- async_mode: bool = False,
403
- cookies: list[dict] | None = None,
404
- ) -> None:
405
- if async_mode:
406
- return
407
- self.struct: dict = {
408
- "conversationId": None,
409
- "clientId": None,
410
- "conversationSignature": None,
411
- "result": {"value": "Success", "message": None},
412
- }
413
- self.proxy = proxy
414
- proxy = (
415
- proxy
416
- or os.environ.get("all_proxy")
417
- or os.environ.get("ALL_PROXY")
418
- or os.environ.get("https_proxy")
419
- or os.environ.get("HTTPS_PROXY")
420
- or None
421
- )
422
- if proxy is not None and proxy.startswith("socks5h://"):
423
- proxy = "socks5://" + proxy[len("socks5h://") :]
424
- self.session = httpx.Client(
425
- proxies=proxy,
426
- timeout=900,
427
- headers=HEADERS_INIT_CONVER,
428
- )
429
- if cookies:
430
- for cookie in cookies:
431
- self.session.cookies.set(cookie["name"], cookie["value"])
432
- # Send GET request
433
- response = self.session.get(
434
- url=os.environ.get("BING_PROXY_URL")
435
- or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
436
- )
437
- if response.status_code != 200:
438
- response = self.session.get(
439
- "https://edge.churchless.tech/edgesvc/turing/conversation/create",
440
- )
441
- if response.status_code != 200:
442
- print(f"Status code: {response.status_code}")
443
- print(response.text)
444
- print(response.url)
445
- raise Exception("Authentication failed")
446
- try:
447
- self.struct = response.json()
448
- except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
449
- raise Exception(
450
- "Authentication failed. You have not been accepted into the beta.",
451
- ) from exc
452
- if self.struct["result"]["value"] == "UnauthorizedRequest":
453
- raise NotAllowedToAccess(self.struct["result"]["message"])
454
-
455
- @staticmethod
456
- async def create(
457
- proxy: str | None = None,
458
- cookies: list[dict] | None = None,
459
- ) -> _Conversation:
460
- self = _Conversation(async_mode=True)
461
- self.struct = {
462
- "conversationId": None,
463
- "clientId": None,
464
- "conversationSignature": None,
465
- "result": {"value": "Success", "message": None},
466
- }
467
- self.proxy = proxy
468
- proxy = (
469
- proxy
470
- or os.environ.get("all_proxy")
471
- or os.environ.get("ALL_PROXY")
472
- or os.environ.get("https_proxy")
473
- or os.environ.get("HTTPS_PROXY")
474
- or None
475
- )
476
- if proxy is not None and proxy.startswith("socks5h://"):
477
- proxy = "socks5://" + proxy[len("socks5h://") :]
478
- transport = httpx.AsyncHTTPTransport(retries=900)
479
- # Convert cookie format to httpx format
480
- formatted_cookies = None
481
- if cookies:
482
- formatted_cookies = httpx.Cookies()
483
- for cookie in cookies:
484
- formatted_cookies.set(cookie["name"], cookie["value"])
485
- async with httpx.AsyncClient(
486
- proxies=proxy,
487
- timeout=30,
488
- headers=HEADERS_INIT_CONVER,
489
- transport=transport,
490
- cookies=formatted_cookies,
491
- ) as client:
492
- # Send GET request
493
- response = await client.get(
494
- url=os.environ.get("BING_PROXY_URL")
495
- or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
496
- )
497
- if response.status_code != 200:
498
- response = await client.get(
499
- "https://edge.churchless.tech/edgesvc/turing/conversation/create",
500
- )
501
- if response.status_code != 200:
502
- print(f"Status code: {response.status_code}")
503
- print(response.text)
504
- print(response.url)
505
- raise Exception("Authentication failed")
506
- try:
507
- self.struct = response.json()
508
- except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
509
- raise Exception(
510
- "Authentication failed. You have not been accepted into the beta.",
511
- ) from exc
512
- if self.struct["result"]["value"] == "UnauthorizedRequest":
513
- raise NotAllowedToAccess(self.struct["result"]["message"])
514
- return self
515
-
516
-
517
- class _ChatHub:
518
- """
519
- Chat API
520
- """
521
-
522
- def __init__(
523
- self,
524
- conversation: _Conversation,
525
- proxy: str = None,
526
- cookies: list[dict] | None = None,
527
- ) -> None:
528
- self.session: aiohttp.ClientSession | None = None
529
- self.wss: aiohttp.ClientWebSocketResponse | None = None
530
- self.request: _ChatHubRequest
531
- self.loop: bool
532
- self.task: asyncio.Task
533
- self.request = _ChatHubRequest(
534
- conversation_signature=conversation.struct["conversationSignature"],
535
- client_id=conversation.struct["clientId"],
536
- conversation_id=conversation.struct["conversationId"],
537
- )
538
- self.cookies = cookies
539
- self.proxy: str = proxy
540
-
541
- async def ask_stream(
542
- self,
543
- prompt: str,
544
- wss_link: str,
545
- conversation_style: CONVERSATION_STYLE_TYPE = None,
546
- raw: bool = False,
547
- options: dict = None,
548
- webpage_context: str | None = None,
549
- search_result: bool = False,
550
- locale: str = "en-US",
551
- ) -> Generator[str, None, None]:
552
- """
553
- Ask a question to the bot
554
- """
555
- timeout = aiohttp.ClientTimeout(total=900)
556
- self.session = aiohttp.ClientSession(timeout=timeout)
557
-
558
- if self.wss and not self.wss.closed:
559
- await self.wss.close()
560
- # Check if websocket is closed
561
- self.wss = await self.session.ws_connect(
562
- wss_link,
563
- headers=HEADERS,
564
- ssl=ssl_context,
565
- proxy=self.proxy,
566
- autoping=False,
567
- )
568
- await self._initial_handshake()
569
- if self.request.invocation_id == 0:
570
- # Construct a ChatHub request
571
- self.request.update(
572
- prompt=prompt,
573
- conversation_style=conversation_style,
574
- options=options,
575
- webpage_context=webpage_context,
576
- search_result=search_result,
577
- locale=locale,
578
- )
579
- else:
580
- async with httpx.AsyncClient() as client:
581
- response = await client.post(
582
- "https://sydney.bing.com/sydney/UpdateConversation/",
583
- json={
584
- "messages": [
585
- {
586
- "author": "user",
587
- "description": webpage_context,
588
- "contextType": "WebPage",
589
- "messageType": "Context",
590
- },
591
- ],
592
- "conversationId": self.request.conversation_id,
593
- "source": "cib",
594
- "traceId": _get_ran_hex(32),
595
- "participant": {"id": self.request.client_id},
596
- "conversationSignature": self.request.conversation_signature,
597
- },
598
- )
599
- if response.status_code != 200:
600
- print(f"Status code: {response.status_code}")
601
- print(response.text)
602
- print(response.url)
603
- raise Exception("Update web page context failed")
604
- # Construct a ChatHub request
605
- self.request.update(
606
- prompt=prompt,
607
- conversation_style=conversation_style,
608
- options=options,
609
- )
610
- # Send request
611
- await self.wss.send_str(_append_identifier(self.request.struct))
612
- final = False
613
- draw = False
614
- resp_txt = ""
615
- result_text = ""
616
- resp_txt_no_link = ""
617
- while not final:
618
- msg = await self.wss.receive(timeout=900)
619
- objects = msg.data.split(DELIMITER)
620
- for obj in objects:
621
- if obj is None or not obj:
622
- continue
623
- response = json.loads(obj)
624
- if response.get("type") != 2 and raw:
625
- yield False, response
626
- elif response.get("type") == 1 and response["arguments"][0].get(
627
- "messages",
628
- ):
629
- if not draw:
630
- if (
631
- response["arguments"][0]["messages"][0].get("messageType")
632
- == "GenerateContentQuery"
633
- ):
634
- async with ImageGenAsync("", True) as image_generator:
635
- images = await image_generator.get_images(
636
- response["arguments"][0]["messages"][0]["text"],
637
- )
638
- for i, image in enumerate(images):
639
- resp_txt = resp_txt + f"\n![image{i}]({image})"
640
- draw = True
641
- if (
642
- response["arguments"][0]["messages"][0]["contentOrigin"]
643
- != "Apology"
644
- ) and not draw:
645
- resp_txt = result_text + response["arguments"][0][
646
- "messages"
647
- ][0]["adaptiveCards"][0]["body"][0].get("text", "")
648
- resp_txt_no_link = result_text + response["arguments"][0][
649
- "messages"
650
- ][0].get("text", "")
651
- if response["arguments"][0]["messages"][0].get(
652
- "messageType",
653
- ):
654
- resp_txt = (
655
- resp_txt
656
- + response["arguments"][0]["messages"][0][
657
- "adaptiveCards"
658
- ][0]["body"][0]["inlines"][0].get("text")
659
- + "\n"
660
- )
661
- result_text = (
662
- result_text
663
- + response["arguments"][0]["messages"][0][
664
- "adaptiveCards"
665
- ][0]["body"][0]["inlines"][0].get("text")
666
- + "\n"
667
- )
668
- yield False, resp_txt
669
-
670
- elif response.get("type") == 2:
671
- if response["item"]["result"].get("error"):
672
- await self.close()
673
- raise Exception(
674
- f"{response['item']['result']['value']}: {response['item']['result']['message']}",
675
- )
676
- if draw:
677
- cache = response["item"]["messages"][1]["adaptiveCards"][0][
678
- "body"
679
- ][0]["text"]
680
- response["item"]["messages"][1]["adaptiveCards"][0]["body"][0][
681
- "text"
682
- ] = (cache + resp_txt)
683
- if (
684
- response["item"]["messages"][-1]["contentOrigin"] == "Apology"
685
- and resp_txt
686
- ):
687
- response["item"]["messages"][-1]["text"] = resp_txt_no_link
688
- response["item"]["messages"][-1]["adaptiveCards"][0]["body"][0][
689
- "text"
690
- ] = resp_txt
691
- print(
692
- "Preserved the message from being deleted",
693
- file=sys.stderr,
694
- )
695
- final = True
696
- await self.close()
697
- yield True, response
698
-
699
- async def _initial_handshake(self) -> None:
700
- await self.wss.send_str(_append_identifier({"protocol": "json", "version": 1}))
701
- await self.wss.receive(timeout=900)
702
-
703
- async def close(self) -> None:
704
- """
705
- Close the connection
706
- """
707
- if self.wss and not self.wss.closed:
708
- await self.wss.close()
709
- if self.session and not self.session.closed:
710
- await self.session.close()
711
-
712
-
713
- class Chatbot:
714
- """
715
- Combines everything to make it seamless
716
- """
717
-
718
- def __init__(
719
- self,
720
- proxy: str | None = None,
721
- cookies: list[dict] | None = None,
722
- ) -> None:
723
- self.proxy: str | None = proxy
724
- self.chat_hub: _ChatHub = _ChatHub(
725
- _Conversation(self.proxy, cookies=cookies),
726
- proxy=self.proxy,
727
- cookies=cookies,
728
- )
729
-
730
- @staticmethod
731
- async def create(
732
- proxy: str | None = None,
733
- cookies: list[dict] | None = None,
734
- ):
735
- self = Chatbot.__new__(Chatbot)
736
- self.proxy = proxy
737
- self.chat_hub = _ChatHub(
738
- await _Conversation.create(self.proxy, cookies=cookies),
739
- proxy=self.proxy,
740
- cookies=cookies,
741
- )
742
- return self
743
-
744
- async def save_conversation(self, filename: str) -> None:
745
- """
746
- Save the conversation to a file
747
- """
748
- async with aiofiles.open(filename, "w") as f:
749
- f.write(json.dumps(self.chat_hub.struct))
750
-
751
- async def load_conversation(self, filename: str) -> None:
752
- """
753
- Load the conversation from a file
754
- """
755
- async with aiofiles.open(filename, "r") as f:
756
- self.chat_hub.struct = json.loads(await f.read())
757
-
758
- async def ask(
759
- self,
760
- prompt: str,
761
- wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
762
- conversation_style: CONVERSATION_STYLE_TYPE = None,
763
- options: dict = None,
764
- webpage_context: str | None = None,
765
- search_result: bool = False,
766
- locale: str = "en-US",
767
- ) -> dict:
768
- """
769
- Ask a question to the bot
770
- """
771
- async for final, response in self.chat_hub.ask_stream(
772
- prompt=prompt,
773
- conversation_style=conversation_style,
774
- wss_link=wss_link,
775
- options=options,
776
- webpage_context=webpage_context,
777
- search_result=search_result,
778
- locale=locale,
779
- ):
780
- if final:
781
- return response
782
- await self.chat_hub.wss.close()
783
- return {}
784
-
785
- async def ask_stream(
786
- self,
787
- prompt: str,
788
- wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
789
- conversation_style: CONVERSATION_STYLE_TYPE = None,
790
- raw: bool = False,
791
- options: dict = None,
792
- webpage_context: str | None = None,
793
- search_result: bool = False,
794
- locale: str = "en-US",
795
- ) -> Generator[str, None, None]:
796
- """
797
- Ask a question to the bot
798
- """
799
- async for response in self.chat_hub.ask_stream(
800
- prompt=prompt,
801
- conversation_style=conversation_style,
802
- wss_link=wss_link,
803
- raw=raw,
804
- options=options,
805
- webpage_context=webpage_context,
806
- search_result=search_result,
807
- locale=locale,
808
- ):
809
- yield response
810
-
811
- async def close(self) -> None:
812
- """
813
- Close the connection
814
- """
815
- await self.chat_hub.close()
816
-
817
- async def reset(self) -> None:
818
- """
819
- Reset the conversation
820
- """
821
- await self.close()
822
- self.chat_hub = _ChatHub(
823
- await _Conversation.create(self.proxy, cookies=self.chat_hub.cookies),
824
- proxy=self.proxy,
825
- cookies=self.chat_hub.cookies,
826
- )
827
-
828
-
829
- async def _get_input_async(
830
- session: PromptSession = None,
831
- completer: WordCompleter = None,
832
- ) -> str:
833
- """
834
- Multiline input function.
835
- """
836
- return await session.prompt_async(
837
- completer=completer,
838
- multiline=True,
839
- auto_suggest=AutoSuggestFromHistory(),
840
- )
841
-
842
-
843
- def _create_session() -> PromptSession:
844
- kb = KeyBindings()
845
-
846
- @kb.add("enter")
847
- def _(event):
848
- buffer_text = event.current_buffer.text
849
- if buffer_text.startswith("!"):
850
- event.current_buffer.validate_and_handle()
851
- else:
852
- event.current_buffer.insert_text("\n")
853
-
854
- @kb.add("escape")
855
- def _(event):
856
- if event.current_buffer.complete_state:
857
- # event.current_buffer.cancel_completion()
858
- event.current_buffer.text = ""
859
-
860
- return PromptSession(key_bindings=kb, history=InMemoryHistory())
861
-
862
-
863
- def _create_completer(commands: list, pattern_str: str = "$"):
864
- return WordCompleter(words=commands, pattern=re.compile(pattern_str))
865
-
866
-
867
- def _create_history_logger(f):
868
- def logger(*args, **kwargs):
869
- tmp = sys.stdout
870
- sys.stdout = f
871
- print(*args, **kwargs, flush=True)
872
- sys.stdout = tmp
873
-
874
- return logger
875
-
876
-
877
- async def async_main(args: argparse.Namespace) -> None:
878
- """
879
- Main function
880
- """
881
- print("Initializing...")
882
- print("Enter `alt+enter` or `escape+enter` to send a message")
883
- # Read and parse cookies
884
- cookies = None
885
- if args.cookie_file:
886
- cookies = json.loads(open(args.cookie_file, encoding="utf-8").read())
887
- bot = await Chatbot.create(proxy=args.proxy, cookies=cookies)
888
- session = _create_session()
889
- completer = _create_completer(["!help", "!exit", "!reset"])
890
- initial_prompt = args.prompt
891
-
892
- # Log chat history
893
- def p_hist(*args, **kwargs):
894
- pass
895
-
896
- if args.history_file:
897
- f = open(args.history_file, "a+", encoding="utf-8")
898
- p_hist = _create_history_logger(f)
899
-
900
- while True:
901
- print("\nYou:")
902
- p_hist("\nYou:")
903
- if initial_prompt:
904
- question = initial_prompt
905
- print(question)
906
- initial_prompt = None
907
- else:
908
- question = (
909
- input()
910
- if args.enter_once
911
- else await _get_input_async(session=session, completer=completer)
912
- )
913
- print()
914
- p_hist(question + "\n")
915
- if question == "!exit":
916
- break
917
- if question == "!help":
918
- print(
919
- """
920
- !help - Show this help message
921
- !exit - Exit the program
922
- !reset - Reset the conversation
923
- """,
924
- )
925
- continue
926
- if question == "!reset":
927
- await bot.reset()
928
- continue
929
- print("Bot:")
930
- p_hist("Bot:")
931
- if args.no_stream:
932
- response = (
933
- await bot.ask(
934
- prompt=question,
935
- conversation_style=args.style,
936
- wss_link=args.wss_link,
937
- search_result=args.search_result,
938
- locale=args.locale,
939
- )
940
- )["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"]
941
- print(response)
942
- p_hist(response)
943
- else:
944
- wrote = 0
945
- if args.rich:
946
- md = Markdown("")
947
- with Live(md, auto_refresh=False) as live:
948
- async for final, response in bot.ask_stream(
949
- prompt=question,
950
- conversation_style=args.style,
951
- wss_link=args.wss_link,
952
- search_result=args.search_result,
953
- locale=args.locale,
954
- ):
955
- if not final:
956
- if not wrote:
957
- p_hist(response, end="")
958
- else:
959
- p_hist(response[wrote:], end="")
960
- if wrote > len(response):
961
- print(md)
962
- print(Markdown("***Bing revoked the response.***"))
963
- wrote = len(response)
964
- md = Markdown(response)
965
- live.update(md, refresh=True)
966
- else:
967
- async for final, response in bot.ask_stream(
968
- prompt=question,
969
- conversation_style=args.style,
970
- wss_link=args.wss_link,
971
- search_result=args.search_result,
972
- locale=args.locale,
973
- ):
974
- if not final:
975
- if not wrote:
976
- print(response, end="", flush=True)
977
- p_hist(response, end="")
978
- else:
979
- print(response[wrote:], end="", flush=True)
980
- p_hist(response[wrote:], end="")
981
- wrote = len(response)
982
- print()
983
- p_hist()
984
- if args.history_file:
985
- f.close()
986
- await bot.close()
987
-
988
-
989
- def main() -> None:
990
- print(
991
- """
992
- EdgeGPT - A demo of reverse engineering the Bing GPT chatbot
993
- Repo: github.com/acheong08/EdgeGPT
994
- By: Antonio Cheong
995
-
996
- !help for help
997
-
998
- Type !exit to exit
999
- """,
1000
- )
1001
- parser = argparse.ArgumentParser()
1002
- parser.add_argument("--enter-once", action="store_true")
1003
- parser.add_argument("--search-result", action="store_true")
1004
- parser.add_argument("--no-stream", action="store_true")
1005
- parser.add_argument("--rich", action="store_true")
1006
- parser.add_argument(
1007
- "--proxy",
1008
- help="Proxy URL (e.g. socks5://127.0.0.1:1080)",
1009
- type=str,
1010
- )
1011
- parser.add_argument(
1012
- "--wss-link",
1013
- help="WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)",
1014
- type=str,
1015
- default="wss://sydney.bing.com/sydney/ChatHub",
1016
- )
1017
- parser.add_argument(
1018
- "--style",
1019
- choices=["creative", "balanced", "precise"],
1020
- default="balanced",
1021
- )
1022
- parser.add_argument(
1023
- "--prompt",
1024
- type=str,
1025
- default="",
1026
- required=False,
1027
- help="prompt to start with",
1028
- )
1029
- parser.add_argument(
1030
- "--cookie-file",
1031
- type=str,
1032
- default="",
1033
- required=False,
1034
- help="path to cookie file",
1035
- )
1036
- parser.add_argument(
1037
- "--history-file",
1038
- type=str,
1039
- default="",
1040
- required=False,
1041
- help="path to history file",
1042
- )
1043
- parser.add_argument(
1044
- "--locale",
1045
- type=str,
1046
- default="en-US",
1047
- required=False,
1048
- help="your locale",
1049
- )
1050
- args = parser.parse_args()
1051
- asyncio.run(async_main(args))
1052
-
1053
-
1054
- class Cookie:
1055
- """
1056
- Convenience class for Bing Cookie files, data, and configuration. This Class
1057
- is updated dynamically by the Query class to allow cycling through >1
1058
- cookie/credentials file e.g. when daily request limits (current 200 per
1059
- account per day) are exceeded.
1060
- """
1061
-
1062
- current_file_index = 0
1063
- dirpath = Path("./").resolve()
1064
- search_pattern = "bing_cookies_*.json"
1065
- ignore_files = set()
1066
- current_filepath: dict | None = None
1067
-
1068
- @classmethod
1069
- def fetch_default(cls, path=None):
1070
- from selenium import webdriver
1071
- from selenium.webdriver.common.by import By
1072
-
1073
- driver = webdriver.Edge()
1074
- driver.get("https://bing.com/chat")
1075
- time.sleep(5)
1076
- xpath = '//button[@id="bnp_btn_accept"]'
1077
- driver.find_element(By.XPATH, xpath).click()
1078
- time.sleep(2)
1079
- xpath = '//a[@id="codexPrimaryButton"]'
1080
- driver.find_element(By.XPATH, xpath).click()
1081
- if path is None:
1082
- path = Path("./bing_cookies__default.json")
1083
- # Double underscore ensures this file is first when sorted
1084
- cookies = driver.get_cookies()
1085
- Path(path).write_text(json.dumps(cookies, indent=4), encoding="utf-8")
1086
- # Path again in case supplied path is: str
1087
- print(f"Cookies saved to: {path}")
1088
- driver.quit()
1089
-
1090
- @classmethod
1091
- def files(cls):
1092
- """Return a sorted list of all cookie files matching .search_pattern"""
1093
- all_files = set(cls.dirpath.glob(cls.search_pattern))
1094
- return sorted(list(all_files - cls.ignore_files))
1095
-
1096
- @classmethod
1097
- def import_data(cls):
1098
- """
1099
- Read the active cookie file and populate the following attributes:
1100
-
1101
- .current_filepath
1102
- .current_data
1103
- .image_token
1104
- """
1105
- try:
1106
- cls.current_filepath = cls.files()[cls.current_file_index]
1107
- except IndexError as exc:
1108
- print(
1109
- "> Please set Cookie.current_filepath to a valid cookie file, then run Cookie.import_data()",
1110
- )
1111
- raise "No valid cookie file found." from exc
1112
- print(f"> Importing cookies from: {cls.current_filepath.name}")
1113
- with open(cls.current_filepath, encoding="utf-8") as file:
1114
- cls.current_data = json.load(file)
1115
- cls.image_token = [x for x in cls.current_data if x.get("name") == "_U"]
1116
- cls.image_token = cls.image_token[0].get("value")
1117
-
1118
- @classmethod
1119
- def import_next(cls):
1120
- """
1121
- Cycle through to the next cookies file. Import it. Mark the previous
1122
- file to be ignored for the remainder of the current session.
1123
- """
1124
- cls.ignore_files.add(cls.current_filepath)
1125
- if Cookie.current_file_index >= len(cls.files()):
1126
- Cookie.current_file_index = 0
1127
- Cookie.import_data()
1128
-
1129
-
1130
- class Query:
1131
- """
1132
- A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input,
1133
- config, and output all together. Relies on Cookie class for authentication
1134
- """
1135
-
1136
- def __init__(
1137
- self,
1138
- prompt,
1139
- style="precise",
1140
- content_type="text",
1141
- cookie_file=0,
1142
- echo=True,
1143
- echo_prompt=False,
1144
- proxy: str | None = None,
1145
- ):
1146
- """
1147
- Arguments:
1148
-
1149
- prompt: Text to enter into Bing Chat
1150
- style: creative, balanced, or precise
1151
- content_type: "text" for Bing Chat; "image" for Dall-e
1152
- cookie_file: Path, filepath string, or index (int) to list of cookie paths
1153
- echo: Print something to confirm request made
1154
- echo_prompt: Print confirmation of the evaluated prompt
1155
- """
1156
- self.proxy = proxy
1157
- self.index = []
1158
- self.request_count = {}
1159
- self.image_dirpath = Path("./").resolve()
1160
- Cookie.import_data()
1161
- self.index += [self]
1162
- self.prompt = prompt
1163
- files = Cookie.files()
1164
- if isinstance(cookie_file, int):
1165
- index = cookie_file if cookie_file < len(files) else 0
1166
- else:
1167
- if not isinstance(cookie_file, (str, Path)):
1168
- message = "'cookie_file' must be an int, str, or Path object"
1169
- raise TypeError(message)
1170
- cookie_file = Path(cookie_file)
1171
- if cookie_file in files: # Supplied filepath IS in Cookie.dirpath
1172
- index = files.index(cookie_file)
1173
- else: # Supplied filepath is NOT in Cookie.dirpath
1174
- if cookie_file.is_file():
1175
- Cookie.dirpath = cookie_file.parent.resolve()
1176
- if cookie_file.is_dir():
1177
- Cookie.dirpath = cookie_file.resolve()
1178
- index = 0
1179
- Cookie.current_file_index = index
1180
- if content_type == "text":
1181
- self.style = style
1182
- self.log_and_send_query(echo, echo_prompt)
1183
- if content_type == "image":
1184
- self.create_image()
1185
-
1186
- def log_and_send_query(self, echo, echo_prompt):
1187
- self.response = asyncio.run(self.send_to_bing(echo, echo_prompt))
1188
- name = str(Cookie.current_filepath.name)
1189
- if not self.request_count.get(name):
1190
- self.request_count[name] = 1
1191
- else:
1192
- self.request_count[name] += 1
1193
-
1194
- def create_image(self):
1195
- image_generator = ImageGen(Cookie.image_token)
1196
- image_generator.save_images(
1197
- image_generator.get_images(self.prompt),
1198
- output_dir=self.image_dirpath,
1199
- )
1200
-
1201
- async def send_to_bing(self, echo=True, echo_prompt=False):
1202
- """Creat, submit, then close a Chatbot instance. Return the response"""
1203
- retries = len(Cookie.files())
1204
- while retries:
1205
- try:
1206
- # Read the cookies file
1207
- bot = await Chatbot.create(
1208
- proxy=self.proxy, cookies=Cookie.current_data
1209
- )
1210
- if echo_prompt:
1211
- print(f"> {self.prompt}=")
1212
- if echo:
1213
- print("> Waiting for response...")
1214
- if self.style.lower() not in "creative balanced precise".split():
1215
- self.style = "precise"
1216
- response = await bot.ask(
1217
- prompt=self.prompt,
1218
- conversation_style=getattr(ConversationStyle, self.style),
1219
- # wss_link="wss://sydney.bing.com/sydney/ChatHub"
1220
- # What other values can this parameter take? It seems to be optional
1221
- )
1222
- return response
1223
- except KeyError:
1224
- print(
1225
- f"> KeyError [{Cookie.current_filepath.name} may have exceeded the daily limit]",
1226
- )
1227
- Cookie.import_next()
1228
- retries -= 1
1229
- finally:
1230
- await bot.close()
1231
-
1232
- @property
1233
- def output(self):
1234
- """The response from a completed Chatbot request"""
1235
- return self.response["item"]["messages"][1]["text"]
1236
-
1237
- @property
1238
- def sources(self):
1239
- """The source names and details parsed from a completed Chatbot request"""
1240
- return self.response["item"]["messages"][1]["sourceAttributions"]
1241
-
1242
- @property
1243
- def sources_dict(self):
1244
- """The source names and details as a dictionary"""
1245
- sources_dict = {}
1246
- name = "providerDisplayName"
1247
- url = "seeMoreUrl"
1248
- for source in self.sources:
1249
- if name in source.keys() and url in source.keys():
1250
- sources_dict[source[name]] = source[url]
1251
- else:
1252
- continue
1253
- return sources_dict
1254
-
1255
- @property
1256
- def code(self):
1257
- """Extract and join any snippets of Python code in the response"""
1258
- code_blocks = self.output.split("```")[1:-1:2]
1259
- code_blocks = ["\n".join(x.splitlines()[1:]) for x in code_blocks]
1260
- return "\n\n".join(code_blocks)
1261
-
1262
- @property
1263
- def languages(self):
1264
- """Extract all programming languages given in code blocks"""
1265
- code_blocks = self.output.split("```")[1:-1:2]
1266
- return {x.splitlines()[0] for x in code_blocks}
1267
-
1268
- @property
1269
- def suggestions(self):
1270
- """Follow-on questions suggested by the Chatbot"""
1271
- return [
1272
- x["text"]
1273
- for x in self.response["item"]["messages"][1]["suggestedResponses"]
1274
- ]
1275
-
1276
- def __repr__(self):
1277
- return f"<EdgeGPT.Query: {self.prompt}>"
1278
-
1279
- def __str__(self):
1280
- return self.output
1281
-
1282
-
1283
- class ImageQuery(Query):
1284
- def __init__(self, prompt, **kwargs):
1285
- kwargs.update({"content_type": "image"})
1286
- super().__init__(prompt, **kwargs)
1287
-
1288
- def __repr__(self):
1289
- return f"<EdgeGPT.ImageQuery: {self.prompt}>"
1290
-
1291
-
1292
- if __name__ == "__main__":
1293
- main()