ka1kuk commited on
Commit
72d4018
1 Parent(s): e0b6fc3

Create Linlada.py

Browse files
Files changed (1) hide show
  1. Linlada.py +1096 -0
Linlada.py ADDED
@@ -0,0 +1,1096 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 time
15
+ import uuid
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import Generator
19
+ from typing import Literal
20
+ from typing import Optional
21
+ from typing import Union
22
+
23
+ import aiohttp
24
+ import certifi
25
+ import httpx
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
+ proxy: str | None = None,
293
+ async_mode: bool = False,
294
+ cookies: list[dict] | None = None,
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
+ if cookies:
321
+ for cookie in cookies:
322
+ self.session.cookies.set(cookie["name"], cookie["value"])
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
+ proxy: str | None = None,
349
+ cookies: list[dict] | 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
+ # Convert cookie format to httpx format
371
+ formatted_cookies = None
372
+ if cookies:
373
+ formatted_cookies = httpx.Cookies()
374
+ for cookie in cookies:
375
+ formatted_cookies.set(cookie["name"], cookie["value"])
376
+ async with httpx.AsyncClient(
377
+ proxies=proxy,
378
+ timeout=30,
379
+ headers=HEADERS_INIT_CONVER,
380
+ transport=transport,
381
+ cookies=formatted_cookies,
382
+ ) as client:
383
+ # Send GET request
384
+ response = await client.get(
385
+ url=os.environ.get("BING_PROXY_URL")
386
+ or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
387
+ )
388
+ if response.status_code != 200:
389
+ response = await client.get(
390
+ "https://edge.churchless.tech/edgesvc/turing/conversation/create",
391
+ )
392
+ if response.status_code != 200:
393
+ print(f"Status code: {response.status_code}")
394
+ print(response.text)
395
+ print(response.url)
396
+ raise Exception("Authentication failed")
397
+ try:
398
+ self.struct = response.json()
399
+ except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
400
+ raise Exception(
401
+ "Authentication failed. You have not been accepted into the beta.",
402
+ ) from exc
403
+ if self.struct["result"]["value"] == "UnauthorizedRequest":
404
+ raise NotAllowedToAccess(self.struct["result"]["message"])
405
+ return self
406
+
407
+
408
+ class _ChatHub:
409
+ """
410
+ Chat API
411
+ """
412
+
413
+ def __init__(
414
+ self,
415
+ conversation: _Conversation,
416
+ proxy: str = None,
417
+ cookies: list[dict] | None = None,
418
+ ) -> None:
419
+ self.session: aiohttp.ClientSession | None = None
420
+ self.wss: aiohttp.ClientWebSocketResponse | None = None
421
+ self.request: _ChatHubRequest
422
+ self.loop: bool
423
+ self.task: asyncio.Task
424
+ self.request = _ChatHubRequest(
425
+ conversation_signature=conversation.struct["conversationSignature"],
426
+ client_id=conversation.struct["clientId"],
427
+ conversation_id=conversation.struct["conversationId"],
428
+ )
429
+ self.cookies = cookies
430
+ self.proxy: str = proxy
431
+
432
+ async def ask_stream(
433
+ self,
434
+ prompt: str,
435
+ wss_link: str,
436
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
437
+ raw: bool = False,
438
+ options: dict = None,
439
+ webpage_context: str | None = None,
440
+ search_result: bool = False,
441
+ ) -> Generator[str, None, None]:
442
+ """
443
+ Ask a question to the bot
444
+ """
445
+ timeout = aiohttp.ClientTimeout(total=30)
446
+ self.session = aiohttp.ClientSession(timeout=timeout)
447
+
448
+ if self.wss and not self.wss.closed:
449
+ await self.wss.close()
450
+ # Check if websocket is closed
451
+ self.wss = await self.session.ws_connect(
452
+ wss_link,
453
+ headers=HEADERS,
454
+ ssl=ssl_context,
455
+ proxy=self.proxy,
456
+ autoping=False,
457
+ )
458
+ await self._initial_handshake()
459
+ if self.request.invocation_id == 0:
460
+ # Construct a ChatHub request
461
+ self.request.update(
462
+ prompt=prompt,
463
+ conversation_style=conversation_style,
464
+ options=options,
465
+ webpage_context=webpage_context,
466
+ search_result=search_result,
467
+ )
468
+ else:
469
+ async with httpx.AsyncClient() as client:
470
+ response = await client.post(
471
+ "https://sydney.bing.com/sydney/UpdateConversation/",
472
+ json={
473
+ "messages": [
474
+ {
475
+ "author": "user",
476
+ "description": webpage_context,
477
+ "contextType": "WebPage",
478
+ "messageType": "Context",
479
+ },
480
+ ],
481
+ "conversationId": self.request.conversation_id,
482
+ "source": "cib",
483
+ "traceId": _get_ran_hex(32),
484
+ "participant": {"id": self.request.client_id},
485
+ "conversationSignature": self.request.conversation_signature,
486
+ },
487
+ )
488
+ if response.status_code != 200:
489
+ print(f"Status code: {response.status_code}")
490
+ print(response.text)
491
+ print(response.url)
492
+ raise Exception("Update web page context failed")
493
+ # Construct a ChatHub request
494
+ self.request.update(
495
+ prompt=prompt,
496
+ conversation_style=conversation_style,
497
+ options=options,
498
+ )
499
+ # Send request
500
+ await self.wss.send_str(_append_identifier(self.request.struct))
501
+ final = False
502
+ draw = False
503
+ resp_txt = ""
504
+ result_text = ""
505
+ resp_txt_no_link = ""
506
+ while not final:
507
+ msg = await self.wss.receive()
508
+ objects = msg.data.split(DELIMITER)
509
+ for obj in objects:
510
+ if obj is None or not obj:
511
+ continue
512
+ response = json.loads(obj)
513
+ if response.get("type") != 2 and raw:
514
+ yield False, response
515
+ elif response.get("type") == 1 and response["arguments"][0].get(
516
+ "messages",
517
+ ):
518
+ if not draw:
519
+ if (
520
+ response["arguments"][0]["messages"][0].get("messageType")
521
+ == "GenerateContentQuery"
522
+ ):
523
+ draw = True
524
+ if (
525
+ response["arguments"][0]["messages"][0]["contentOrigin"]
526
+ != "Apology"
527
+ ) and not draw:
528
+ resp_txt = result_text + response["arguments"][0][
529
+ "messages"
530
+ ][0]["adaptiveCards"][0]["body"][0].get("text", "")
531
+ resp_txt_no_link = result_text + response["arguments"][0][
532
+ "messages"
533
+ ][0].get("text", "")
534
+ if response["arguments"][0]["messages"][0].get(
535
+ "messageType",
536
+ ):
537
+ resp_txt = (
538
+ resp_txt
539
+ + response["arguments"][0]["messages"][0][
540
+ "adaptiveCards"
541
+ ][0]["body"][0]["inlines"][0].get("text")
542
+ + "\n"
543
+ )
544
+ result_text = (
545
+ result_text
546
+ + response["arguments"][0]["messages"][0][
547
+ "adaptiveCards"
548
+ ][0]["body"][0]["inlines"][0].get("text")
549
+ + "\n"
550
+ )
551
+ yield False, resp_txt
552
+
553
+ elif response.get("type") == 2:
554
+ if response["item"]["result"].get("error"):
555
+ await self.close()
556
+ raise Exception(
557
+ f"{response['item']['result']['value']}: {response['item']['result']['message']}",
558
+ )
559
+ if draw:
560
+ cache = response["item"]["messages"][1]["adaptiveCards"][0][
561
+ "body"
562
+ ][0]["text"]
563
+ response["item"]["messages"][1]["adaptiveCards"][0]["body"][0][
564
+ "text"
565
+ ] = (cache + resp_txt)
566
+ if (
567
+ response["item"]["messages"][-1]["contentOrigin"] == "Apology"
568
+ and resp_txt
569
+ ):
570
+ response["item"]["messages"][-1]["text"] = resp_txt_no_link
571
+ response["item"]["messages"][-1]["adaptiveCards"][0]["body"][0][
572
+ "text"
573
+ ] = resp_txt
574
+ print(
575
+ "Preserved the message from being deleted",
576
+ file=sys.stderr,
577
+ )
578
+ final = True
579
+ await self.close()
580
+ yield True, response
581
+
582
+ async def _initial_handshake(self) -> None:
583
+ await self.wss.send_str(_append_identifier({"protocol": "json", "version": 1}))
584
+ await self.wss.receive()
585
+
586
+ async def close(self) -> None:
587
+ """
588
+ Close the connection
589
+ """
590
+ if self.wss and not self.wss.closed:
591
+ await self.wss.close()
592
+ if self.session and not self.session.closed:
593
+ await self.session.close()
594
+
595
+
596
+ class Chatbot:
597
+ """
598
+ Combines everything to make it seamless
599
+ """
600
+
601
+ def __init__(
602
+ self,
603
+ proxy: str | None = None,
604
+ cookies: list[dict] | None = None,
605
+ ) -> None:
606
+ self.proxy: str | None = proxy
607
+ self.chat_hub: _ChatHub = _ChatHub(
608
+ _Conversation(self.proxy, cookies=cookies),
609
+ proxy=self.proxy,
610
+ cookies=cookies,
611
+ )
612
+
613
+ @staticmethod
614
+ async def create(
615
+ proxy: str | None = None,
616
+ cookies: list[dict] | None = None,
617
+ ):
618
+ self = Chatbot.__new__(Chatbot)
619
+ self.proxy = proxy
620
+ self.chat_hub = _ChatHub(
621
+ await _Conversation.create(self.proxy, cookies=cookies),
622
+ proxy=self.proxy,
623
+ cookies=cookies,
624
+ )
625
+ return self
626
+
627
+ async def ask(
628
+ self,
629
+ prompt: str,
630
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
631
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
632
+ options: dict = None,
633
+ webpage_context: str | None = None,
634
+ search_result: bool = False,
635
+ ) -> dict:
636
+ """
637
+ Ask a question to the bot
638
+ """
639
+ async for final, response in self.chat_hub.ask_stream(
640
+ prompt=prompt,
641
+ conversation_style=conversation_style,
642
+ wss_link=wss_link,
643
+ options=options,
644
+ webpage_context=webpage_context,
645
+ search_result=search_result,
646
+ ):
647
+ if final:
648
+ return response
649
+ await self.chat_hub.wss.close()
650
+ return {}
651
+
652
+ async def ask_stream(
653
+ self,
654
+ prompt: str,
655
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
656
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
657
+ raw: bool = False,
658
+ options: dict = None,
659
+ webpage_context: str | None = None,
660
+ search_result: bool = False,
661
+ ) -> Generator[str, None, None]:
662
+ """
663
+ Ask a question to the bot
664
+ """
665
+ async for response in self.chat_hub.ask_stream(
666
+ prompt=prompt,
667
+ conversation_style=conversation_style,
668
+ wss_link=wss_link,
669
+ raw=raw,
670
+ options=options,
671
+ webpage_context=webpage_context,
672
+ search_result=search_result,
673
+ ):
674
+ yield response
675
+
676
+ async def close(self) -> None:
677
+ """
678
+ Close the connection
679
+ """
680
+ await self.chat_hub.close()
681
+
682
+ async def reset(self) -> None:
683
+ """
684
+ Reset the conversation
685
+ """
686
+ await self.close()
687
+ self.chat_hub = _ChatHub(
688
+ await _Conversation.create(self.proxy),
689
+ proxy=self.proxy,
690
+ )
691
+
692
+
693
+ async def _get_input_async(
694
+ session: PromptSession = None,
695
+ completer: WordCompleter = None,
696
+ ) -> str:
697
+ """
698
+ Multiline input function.
699
+ """
700
+ return await session.prompt_async(
701
+ completer=completer,
702
+ multiline=True,
703
+ auto_suggest=AutoSuggestFromHistory(),
704
+ )
705
+
706
+
707
+ def _create_session() -> PromptSession:
708
+ kb = KeyBindings()
709
+
710
+ @kb.add("enter")
711
+ def _(event):
712
+ buffer_text = event.current_buffer.text
713
+ if buffer_text.startswith("!"):
714
+ event.current_buffer.validate_and_handle()
715
+ else:
716
+ event.current_buffer.insert_text("\n")
717
+
718
+ @kb.add("escape")
719
+ def _(event):
720
+ if event.current_buffer.complete_state:
721
+ # event.current_buffer.cancel_completion()
722
+ event.current_buffer.text = ""
723
+
724
+ return PromptSession(key_bindings=kb, history=InMemoryHistory())
725
+
726
+
727
+ def _create_completer(commands: list, pattern_str: str = "$"):
728
+ return WordCompleter(words=commands, pattern=re.compile(pattern_str))
729
+
730
+
731
+ async def async_main(args: argparse.Namespace) -> None:
732
+ """
733
+ Main function
734
+ """
735
+ print("Initializing...")
736
+ print("Enter `alt+enter` or `escape+enter` to send a message")
737
+ # Read and parse cookies
738
+ cookies = None
739
+ if args.cookie_file:
740
+ cookies = json.loads(open(args.cookie_file, encoding="utf-8").read())
741
+ bot = await Chatbot.create(proxy=args.proxy, cookies=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
+ "--prompt",
851
+ type=str,
852
+ default="",
853
+ required=False,
854
+ help="prompt to start with",
855
+ )
856
+ parser.add_argument(
857
+ "--cookie-file",
858
+ type=str,
859
+ default="",
860
+ required=False,
861
+ help="path to cookie file",
862
+ )
863
+ args = parser.parse_args()
864
+ asyncio.run(async_main(args))
865
+
866
+
867
+ class Cookie:
868
+ """
869
+ Convenience class for Bing Cookie files, data, and configuration. This Class
870
+ is updated dynamically by the Query class to allow cycling through >1
871
+ cookie/credentials file e.g. when daily request limits (current 200 per
872
+ account per day) are exceeded.
873
+ """
874
+
875
+ current_file_index = 0
876
+ dirpath = Path("./").resolve()
877
+ search_pattern = "bing_cookies_*.json"
878
+ ignore_files = set()
879
+
880
+ @classmethod
881
+ def fetch_default(cls, path=None):
882
+ from selenium import webdriver
883
+ from selenium.webdriver.common.by import By
884
+
885
+ driver = webdriver.Edge()
886
+ driver.get("https://bing.com/chat")
887
+ time.sleep(5)
888
+ xpath = '//button[@id="bnp_btn_accept"]'
889
+ driver.find_element(By.XPATH, xpath).click()
890
+ time.sleep(2)
891
+ xpath = '//a[@id="codexPrimaryButton"]'
892
+ driver.find_element(By.XPATH, xpath).click()
893
+ if path is None:
894
+ path = Path("./bing_cookies__default.json")
895
+ # Double underscore ensures this file is first when sorted
896
+ cookies = driver.get_cookies()
897
+ Path(path).write_text(json.dumps(cookies, indent=4), encoding="utf-8")
898
+ # Path again in case supplied path is: str
899
+ print(f"Cookies saved to: {path}")
900
+ driver.quit()
901
+
902
+ @classmethod
903
+ def files(cls):
904
+ """Return a sorted list of all cookie files matching .search_pattern"""
905
+ all_files = set(cls.dirpath.glob(cls.search_pattern))
906
+ return sorted(list(all_files - cls.ignore_files))
907
+
908
+ @classmethod
909
+ def import_data(cls):
910
+ """
911
+ Read the active cookie file and populate the following attributes:
912
+
913
+ .current_filepath
914
+ .current_data
915
+ .image_token
916
+ """
917
+ try:
918
+ cls.current_filepath = cls.files()[cls.current_file_index]
919
+ except IndexError:
920
+ print(
921
+ "> Please set Cookie.current_filepath to a valid cookie file, then run Cookie.import_data()",
922
+ )
923
+ return
924
+ print(f"> Importing cookies from: {cls.current_filepath.name}")
925
+ with open(cls.current_filepath, encoding="utf-8") as file:
926
+ cls.current_data = json.load(file)
927
+ cls.image_token = [x for x in cls.current_data if x.get("name") == "_U"]
928
+ cls.image_token = cls.image_token[0].get("value")
929
+
930
+ @classmethod
931
+ def import_next(cls):
932
+ """
933
+ Cycle through to the next cookies file. Import it. Mark the previous
934
+ file to be ignored for the remainder of the current session.
935
+ """
936
+ cls.ignore_files.add(cls.current_filepath)
937
+ if Cookie.current_file_index >= len(cls.files()):
938
+ Cookie.current_file_index = 0
939
+ Cookie.import_data()
940
+
941
+
942
+ class Query:
943
+ """
944
+ A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input,
945
+ config, and output all together. Relies on Cookie class for authentication
946
+ """
947
+
948
+ def __init__(
949
+ self,
950
+ prompt,
951
+ style="precise",
952
+ content_type="text",
953
+ cookie_file=0,
954
+ echo=True,
955
+ echo_prompt=False,
956
+ ):
957
+ """
958
+ Arguments:
959
+
960
+ prompt: Text to enter into Bing Chat
961
+ style: creative, balanced, or precise
962
+ content_type: "text" for Bing Chat; "image" for Dall-e
963
+ cookie_file: Path, filepath string, or index (int) to list of cookie paths
964
+ echo: Print something to confirm request made
965
+ echo_prompt: Print confirmation of the evaluated prompt
966
+ """
967
+ self.index = []
968
+ self.request_count = {}
969
+ self.image_dirpath = Path("./").resolve()
970
+ Cookie.import_data()
971
+ self.index += [self]
972
+ self.prompt = prompt
973
+ files = Cookie.files()
974
+ if isinstance(cookie_file, int):
975
+ index = cookie_file if cookie_file < len(files) else 0
976
+ else:
977
+ if not isinstance(cookie_file, (str, Path)):
978
+ message = "'cookie_file' must be an int, str, or Path object"
979
+ raise TypeError(message)
980
+ cookie_file = Path(cookie_file)
981
+ if cookie_file in files(): # Supplied filepath IS in Cookie.dirpath
982
+ index = files.index(cookie_file)
983
+ else: # Supplied filepath is NOT in Cookie.dirpath
984
+ if cookie_file.is_file():
985
+ Cookie.dirpath = cookie_file.parent.resolve()
986
+ if cookie_file.is_dir():
987
+ Cookie.dirpath = cookie_file.resolve()
988
+ index = 0
989
+ Cookie.current_file_index = index
990
+ if content_type == "text":
991
+ self.style = style
992
+ self.log_and_send_query(echo, echo_prompt)
993
+ if content_type == "image":
994
+ self.create_image()
995
+
996
+ def log_and_send_query(self, echo, echo_prompt):
997
+ self.response = asyncio.run(self.send_to_bing(echo, echo_prompt))
998
+ name = str(Cookie.current_filepath.name)
999
+ if not self.request_count.get(name):
1000
+ self.request_count[name] = 1
1001
+ else:
1002
+ self.request_count[name] += 1
1003
+
1004
+ def create_image(self):
1005
+ print('Image cannot available!')
1006
+
1007
+ async def send_to_bing(self, echo=True, echo_prompt=False):
1008
+ """Creat, submit, then close a Chatbot instance. Return the response"""
1009
+ retries = len(Cookie.files())
1010
+ while retries:
1011
+ try:
1012
+ bot = await Chatbot.create()
1013
+ if echo_prompt:
1014
+ print(f"> {self.prompt=}")
1015
+ if echo:
1016
+ print("> Waiting for response...")
1017
+ if self.style.lower() not in "creative balanced precise".split():
1018
+ self.style = "precise"
1019
+ response = await bot.ask(
1020
+ prompt=self.prompt,
1021
+ conversation_style=getattr(ConversationStyle, self.style),
1022
+ # wss_link="wss://sydney.bing.com/sydney/ChatHub"
1023
+ # What other values can this parameter take? It seems to be optional
1024
+ )
1025
+ return response
1026
+ except KeyError:
1027
+ print(
1028
+ f"> KeyError [{Cookie.current_filepath.name} may have exceeded the daily limit]",
1029
+ )
1030
+ Cookie.import_next()
1031
+ retries -= 1
1032
+ finally:
1033
+ await bot.close()
1034
+
1035
+ @property
1036
+ def output(self):
1037
+ """The response from a completed Chatbot request"""
1038
+ return self.response["item"]["messages"][1]["text"]
1039
+
1040
+ @property
1041
+ def sources(self):
1042
+ """The source names and details parsed from a completed Chatbot request"""
1043
+ return self.response["item"]["messages"][1]["sourceAttributions"]
1044
+
1045
+ @property
1046
+ def sources_dict(self):
1047
+ """The source names and details as a dictionary"""
1048
+ sources_dict = {}
1049
+ name = "providerDisplayName"
1050
+ url = "seeMoreUrl"
1051
+ for source in self.sources:
1052
+ if name in source.keys() and url in source.keys():
1053
+ sources_dict[source[name]] = source[url]
1054
+ else:
1055
+ continue
1056
+ return sources_dict
1057
+
1058
+ @property
1059
+ def code(self):
1060
+ """Extract and join any snippets of Python code in the response"""
1061
+ code_blocks = self.output.split("```")[1:-1:2]
1062
+ code_blocks = ["\n".join(x.splitlines()[1:]) for x in code_blocks]
1063
+ return "\n\n".join(code_blocks)
1064
+
1065
+ @property
1066
+ def languages(self):
1067
+ """Extract all programming languages given in code blocks"""
1068
+ code_blocks = self.output.split("```")[1:-1:2]
1069
+ return {x.splitlines()[0] for x in code_blocks}
1070
+
1071
+ @property
1072
+ def suggestions(self):
1073
+ """Follow-on questions suggested by the Chatbot"""
1074
+ return [
1075
+ x["text"]
1076
+ for x in self.response["item"]["messages"][1]["suggestedResponses"]
1077
+ ]
1078
+
1079
+ def __repr__(self):
1080
+ return f"<EdgeGPT.Query: {self.prompt}>"
1081
+
1082
+ def __str__(self):
1083
+ return self.output
1084
+
1085
+
1086
+ class ImageQuery(Query):
1087
+ def __init__(self, prompt, **kwargs):
1088
+ kwargs.update({"content_type": "image"})
1089
+ super().__init__(prompt, **kwargs)
1090
+
1091
+ def __repr__(self):
1092
+ return f"<EdgeGPT.ImageQuery: {self.prompt}>"
1093
+
1094
+
1095
+ if __name__ == "__main__":
1096
+ main()