Senkaro commited on
Commit
b90c2f0
1 Parent(s): bec9b3d

Upload bot_chat.py

Browse files
Files changed (1) hide show
  1. bot_chat.py +455 -0
bot_chat.py ADDED
@@ -0,0 +1,455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import datetime
3
+ import json
4
+ import re
5
+ import uuid
6
+ import urllib.parse
7
+ import contextlib
8
+ from datetime import datetime, timedelta
9
+ from dateutil.parser import parse as dateparse
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+ from typing import List, Optional
13
+ from websockets import exceptions as ws_exceptions
14
+
15
+ import aiohttp
16
+ import websockets
17
+ import bot_img
18
+ import bot_strings
19
+
20
+ MESSAGE_CREDS = {}
21
+
22
+ SEMAPHORE_ITEMS = {}
23
+
24
+
25
+ PENDING_REQUESTS = {}
26
+
27
+ URL = 'wss://sydney.bing.com/sydney/ChatHub'
28
+
29
+
30
+ class ChatHubException(Exception):
31
+ pass
32
+
33
+
34
+ @dataclass
35
+ class SydRenderCard:
36
+ text: Optional[str]
37
+ url: str
38
+
39
+
40
+ @dataclass
41
+ class ResponseTypeText:
42
+ answer: str
43
+ cards: List[str]
44
+ render_card: Optional[SydRenderCard]
45
+
46
+
47
+ @dataclass
48
+ class ResponseTypeImage:
49
+ images: List[str]
50
+ caption: str
51
+
52
+
53
+ class Style(Enum):
54
+ CREATIVE = 1
55
+ BALANCED = 2
56
+ PRECISE = 3
57
+ def __str__(self) -> str:
58
+ return self.name.lower().title()
59
+
60
+
61
+ def read_until_separator(message):
62
+ out = ""
63
+ for x in message:
64
+ if x == '\u001e':
65
+ break
66
+ out += x
67
+ return out
68
+
69
+
70
+ async def clear_session(userID):
71
+ global MESSAGE_CREDS
72
+ if userID in MESSAGE_CREDS.keys():
73
+ del MESSAGE_CREDS[userID]
74
+ return True
75
+ return False
76
+
77
+
78
+ async def get_session(userID):
79
+ return MESSAGE_CREDS[userID] if userID in MESSAGE_CREDS.keys() else None
80
+
81
+ async def prepare_request(uuid):
82
+ global PENDING_REQUESTS
83
+ PENDING_REQUESTS[uuid] = None
84
+
85
+ async def set_pending(uuid, ws):
86
+ global PENDING_REQUESTS
87
+ if (await is_queued(uuid)):
88
+ PENDING_REQUESTS[uuid] = ws
89
+
90
+
91
+ async def cancel_request(uuid):
92
+ global PENDING_REQUESTS
93
+ if uuid in PENDING_REQUESTS.keys():
94
+ if PENDING_REQUESTS[uuid]:
95
+ ws = PENDING_REQUESTS[uuid]
96
+ if ws.open:
97
+ await ws.close()
98
+ del PENDING_REQUESTS[uuid]
99
+ return True
100
+ return False
101
+
102
+
103
+ async def is_pending(uuid):
104
+ return uuid in PENDING_REQUESTS.keys() and PENDING_REQUESTS[uuid]
105
+
106
+
107
+ async def is_queued(uuid):
108
+ return uuid in PENDING_REQUESTS.keys() and not PENDING_REQUESTS[uuid]
109
+
110
+
111
+ async def finish_request(uuid):
112
+ global PENDING_REQUESTS
113
+ return await cancel_request(uuid)
114
+
115
+
116
+ async def create_session(cookies):
117
+ chat_session = {}
118
+ error = None
119
+ headers = {
120
+ 'User-Agent':
121
+ 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0 BingSapphire/24.1.410310303',
122
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
123
+ }
124
+ async with aiohttp.ClientSession(headers=headers, cookie_jar=cookies) as session:
125
+ async with session.get('https://www.bing.com/turing/conversation/create') as response:
126
+ if response.status == 200:
127
+ js = await response.json()
128
+ if 'result' in js.keys():
129
+ result = js['result']
130
+ if result['message']:
131
+ error = result['message']
132
+ else:
133
+ chat_session['traceID'] = uuid.uuid4()
134
+ chat_session['clientID'] = js['clientId']
135
+ chat_session['conversationId'] = js['conversationId']
136
+ chat_session['conversationSignature'] = js['conversationSignature']
137
+ return chat_session, error
138
+
139
+
140
+ async def send_message(userID, message, cookies, style, retry_on_disconnect=True, request_id=None):
141
+ global SEMAPHORE_ITEMS
142
+ if userID not in SEMAPHORE_ITEMS.keys():
143
+ SEMAPHORE_ITEMS[userID] = asyncio.Semaphore(1)
144
+ seconds = 120
145
+ if (await is_queued(request_id) or not (await is_pending(request_id))):
146
+ async with SEMAPHORE_ITEMS[userID]:
147
+ try:
148
+ result = await asyncio.wait_for(_send_message(userID, message, cookies, style, retry_on_disconnect=retry_on_disconnect, request_id=request_id), timeout=seconds)
149
+ return result
150
+ except asyncio.TimeoutError:
151
+ raise asyncio.TimeoutError(
152
+ f"Function {_send_message.__name__} timed out after {seconds} seconds")
153
+
154
+
155
+ async def _send_message(userID, message, cookies, style, retry_on_disconnect=True, request_id=None):
156
+ global MESSAGE_CREDS
157
+ chat_session = None
158
+ answer = None
159
+ update = None
160
+ image_query = None
161
+ try_again = False
162
+ cards = []
163
+ render_card = None
164
+ last_message_type = -1
165
+ if userID not in MESSAGE_CREDS.keys():
166
+ chat_session, error = await create_session(cookies)
167
+ if error:
168
+ raise ChatHubException(error)
169
+ chat_session['isStartOfSession'] = True
170
+ chat_session['style'] = style
171
+ chat_session['invocationId'] = 0
172
+ chat_session['conversationExpiryTime'] = datetime.utcnow() + timedelta(hours=4)
173
+ chat_session['numRemainingUserMessagesInConversation'] = None
174
+
175
+ MESSAGE_CREDS[userID] = chat_session
176
+ else:
177
+ chat_session = MESSAGE_CREDS[userID]
178
+ if chat_session['style'] != style:
179
+ del MESSAGE_CREDS[userID]
180
+ return await send_message(userID, message, cookies, style, retry_on_disconnect=retry_on_disconnect, request_id=request_id)
181
+ if chat_session['conversationExpiryTime'].timestamp() < datetime.utcnow().timestamp():
182
+ del MESSAGE_CREDS[userID]
183
+ return await send_message(userID, message, cookies, style, retry_on_disconnect=retry_on_disconnect, request_id=request_id)
184
+ if chat_session['numRemainingUserMessagesInConversation'] == 0:
185
+ del MESSAGE_CREDS[userID]
186
+ return await send_message(userID, message, cookies, style, retry_on_disconnect=retry_on_disconnect, request_id=request_id)
187
+ chat_session['isStartOfSession'] = False
188
+
189
+ chat_session['question'] = message
190
+
191
+ ws_messages = []
192
+ message_payload = await build_message(**chat_session)
193
+ with contextlib.suppress(ws_exceptions.ConnectionClosedOK):
194
+ async with websockets.connect(URL, ssl=True, ping_timeout=None,
195
+ ping_interval=None,
196
+ extensions=[
197
+ websockets.extensions.permessage_deflate.ClientPerMessageDeflateFactory(
198
+ server_max_window_bits=11,
199
+ client_max_window_bits=11,
200
+ compress_settings={
201
+ 'memLevel': 4},
202
+ ), ]) as ws:
203
+ await set_pending(request_id, ws)
204
+ await ws.send('{"protocol":"json","version":1}')
205
+ await ws.recv()
206
+ await ws.send('{"type":6}')
207
+ await ws.send(json.dumps(message_payload) + '')
208
+ async for responses in ws:
209
+ if not (await is_pending(request_id)):
210
+ break
211
+ js = json.loads(read_until_separator(responses))
212
+ if (
213
+ js['type'] == 2
214
+ and js['item']['result']['value'] == 'Throttled'
215
+ ):
216
+ raise ChatHubException(bot_strings.RATELIMIT_STRING)
217
+ last_message_type = js['type']
218
+ if last_message_type == 6:
219
+ await ws.send('{"type":6}')
220
+ elif last_message_type == 1:
221
+ ws_messages.append(js)
222
+ elif last_message_type == 2:
223
+ ws_messages.append(js)
224
+ break
225
+ elif last_message_type == 7:
226
+ if js['allowReconnect'] and retry_on_disconnect:
227
+ try_again = True
228
+ break
229
+ raise ChatHubException(
230
+ bot_strings.CLOSE_MESSAGE_RECEIVED_STRING)
231
+ else:
232
+ break
233
+ if try_again:
234
+ return await send_message(userID=userID, message=message, cookies=cookies, style=style, retry_on_disconnect=False, request_id=request_id)
235
+ if ws_messages:
236
+ for responses in ws_messages:
237
+ if responses['type'] == 1 and 'arguments' in responses.keys():
238
+ argument = responses['arguments'][-1]
239
+ if messages := argument.get('messages'):
240
+ messages = responses['arguments'][-1]['messages']
241
+ for response in messages:
242
+ if 'text' in response.keys() and response['author'] == 'bot' and 'messageType' not in response.keys():
243
+ if response['text']:
244
+ update = response['text']
245
+ if 'adaptiveCards' in response.keys() and len(response['adaptiveCards']) > 0:
246
+ _cards = []
247
+ for _card in response['adaptiveCards']:
248
+ if _card['type'] == 'AdaptiveCard':
249
+ card = _card['body'][-1]['text']
250
+ markdown_pattern = re.findall(
251
+ r'\[(.*?)\]\((.*?)\)', card)
252
+ _cards.extend(
253
+ iter(markdown_pattern))
254
+ cards = _cards
255
+ elif 'adaptiveCards' in response.keys() and len(response['adaptiveCards']) > 0:
256
+ body = response['adaptiveCards'][-1]['body'][0]
257
+ if 'text' in body.keys():
258
+ update = response['adaptiveCards'][-1]['body'][0]['text']
259
+ if responses['type'] == 2:
260
+ cards = []
261
+ item = responses['item']
262
+ if 'result' in item.keys():
263
+ if 'error' in item['result']:
264
+ raise ChatHubException(
265
+ item['result']['error']['message'])
266
+ if 'messages' in item.keys():
267
+ if 'conversationExpiryTime' in item.keys() and item['conversationExpiryTime']:
268
+ chat_session['conversationExpiryTime'] = dateparse(
269
+ item['conversationExpiryTime'])
270
+ if 'throttling' in item.keys() and 'messages' in item.keys():
271
+ if 'throttling' in responses['item']:
272
+ chat_session['numRemainingUserMessagesInConversation'] = responses['item']['throttling'][
273
+ 'maxNumUserMessagesInConversation'] - responses['item']['throttling']['numUserMessagesInConversation']
274
+ for response in item['messages']:
275
+ if response['author'] == 'bot' and 'messageType' not in response.keys() and 'text' in response.keys():
276
+ if response['text']:
277
+ answer = response['text']
278
+ if 'adaptiveCards' in response.keys():
279
+ for _card in response['adaptiveCards']:
280
+ if _card['type'] == 'AdaptiveCard':
281
+ card = _card['body'][-1]['text']
282
+ markdown_pattern = re.findall(
283
+ r'\[(.*?)\]\((.*?)\)', card)
284
+ cards.extend(
285
+ iter(markdown_pattern))
286
+ elif response['author'] == 'bot' and 'messageType' in response.keys() and response['messageType'] == 'RenderCardRequest':
287
+ uri = 'https://www.bing.com/search?q='
288
+ render_card = SydRenderCard(
289
+ response["text"], f'{uri}{urllib.parse.quote(response["text"])}')
290
+ elif 'adaptiveCards' in response.keys() and len(response['adaptiveCards']) > 0:
291
+ body = response['adaptiveCards'][-1]['body'][0]
292
+ if 'text' in body:
293
+ answer = response['adaptiveCards'][-1]['body'][0]['text']
294
+ if 'contentType' in response.keys() and response['contentType'] == 'IMAGE':
295
+ image_query = response['text']
296
+ if 'contentOrigin' in response.keys() and response['contentOrigin'] == 'Apology':
297
+ answer = response['adaptiveCards'][0]['body'][0]['text']
298
+ if 'messageType' in response.keys() and response['messageType'] == 'Disengaged' and userID in MESSAGE_CREDS.keys():
299
+ del MESSAGE_CREDS[userID]
300
+ break
301
+ if image_query:
302
+ images, error, canceled = await bot_img.generate_image(userID, response['text'], cookies, request_id=request_id)
303
+ if error:
304
+ await finish_request(request_id)
305
+ raise ChatHubException(error)
306
+ if images:
307
+ await finish_request(request_id)
308
+ return ResponseTypeImage(images, response['text'])
309
+ if not answer and not update:
310
+ if render_card:
311
+ answer = f'[{render_card.text}]({render_card.url})'
312
+ if last_message_type not in [-1, 1, 2, 6, 7]:
313
+ await finish_request(request_id)
314
+ raise ChatHubException(
315
+ f'{bot_strings.PROCESSING_ERROR_STRING}: {last_message_type}')
316
+ await finish_request(request_id)
317
+ return ResponseTypeText(answer or update, cards, render_card)
318
+
319
+
320
+ async def build_message(question, clientID, traceID, conversationId, conversationSignature, isStartOfSession, style, invocationId, **kwargs):
321
+ global MESSAGE_CREDS
322
+ now = datetime.now()
323
+ formatted_date = now.strftime('%Y-%m-%dT%H:%M:%S%z')
324
+
325
+ optionsSets = [
326
+ "nlu_direct_response_filter",
327
+ "deepleo",
328
+ "disable_emoji_spoken_text",
329
+ "responsible_ai_policy_235",
330
+ "enablemm",
331
+ "weanow",
332
+ "uquopt",
333
+ "rai271",
334
+ "refpromptv1",
335
+ "smartname",
336
+ "mvcargnd",
337
+ "dagslnv1nr",
338
+ "dv3sugg",
339
+ "autosave",
340
+ "galileo",
341
+ "saharagenconv5"
342
+ ]
343
+ if style == Style.CREATIVE:
344
+ optionsSets = [
345
+ "nlu_direct_response_filter",
346
+ "deepleo",
347
+ "disable_emoji_spoken_text",
348
+ "responsible_ai_policy_235",
349
+ "enablemm",
350
+ "weanow",
351
+ "uquopt",
352
+ "rai271",
353
+ "refpromptv1",
354
+ "smartname",
355
+ "mvcargnd",
356
+ "dagslnv1nr",
357
+ "dv3sugg",
358
+ "autosave",
359
+ "h3imaginative",
360
+ "clgalileo",
361
+ "gencontentv3"
362
+ ]
363
+ if style == Style.PRECISE:
364
+ optionsSets = [
365
+ "nlu_direct_response_filter",
366
+ "deepleo",
367
+ "disable_emoji_spoken_text",
368
+ "responsible_ai_policy_235",
369
+ "enablemm",
370
+ "weanow",
371
+ "uquopt",
372
+ "rai271",
373
+ "refpromptv1",
374
+ "smartname",
375
+ "mvcargnd",
376
+ "dagslnv1nr",
377
+ "dv3sugg",
378
+ "autosave",
379
+ "h3precise",
380
+ "clgalileo",
381
+ "gencontentv3"
382
+ ]
383
+ payload = {
384
+ "arguments": [
385
+ {
386
+ "source": "cib",
387
+ "optionsSets": optionsSets,
388
+ "allowedMessageTypes": [
389
+ "ActionRequest",
390
+ "Chat",
391
+ "Context",
392
+ "InternalSearchQuery",
393
+ "InternalSearchResult",
394
+ "Disengaged",
395
+ "InternalLoaderMessage",
396
+ "Progress",
397
+ "RenderCardRequest",
398
+ "AdsQuery",
399
+ "SemanticSerp",
400
+ "GenerateContentQuery",
401
+ "SearchQuery"
402
+ ],
403
+ "sliceIds": [
404
+ "winmuid1tf",
405
+ "anssupltmr2",
406
+ "rankcf",
407
+ "production",
408
+ "tts2",
409
+ "voicelang2",
410
+ "anssupfo2",
411
+ "revpayaad",
412
+ "winlongmsg2tf",
413
+ "sydtransl",
414
+ "606rai271",
415
+ "602refusal",
416
+ "0510wow",
417
+ "wowcds",
418
+ "516ajcome",
419
+ "621docxfmtho",
420
+ "threadstitle",
421
+ "615scss0",
422
+ "tempcacheread",
423
+ "temptacache",
424
+ "619dagslnv1nr"
425
+ ],
426
+ "verbosity": "verbose",
427
+ "traceId": str(traceID),
428
+ "isStartOfSession": isStartOfSession,
429
+ "message": {
430
+ "locale": "en-US",
431
+ "market": "en-US",
432
+ "region": "WW",
433
+ "location": "",
434
+ "locationHints": [
435
+ ],
436
+ "timestamp": formatted_date,
437
+ "author": "user",
438
+ "inputMethod": "Keyboard",
439
+ "text": question,
440
+ "messageType": "Chat"
441
+ },
442
+ "tone": str(style),
443
+ "conversationSignature": conversationSignature,
444
+ "participant": {
445
+ "id": clientID
446
+ },
447
+ "spokenTextMode": "None",
448
+ "conversationId": conversationId,
449
+ }
450
+ ],
451
+ "invocationId": f'{invocationId}',
452
+ "target": "chat",
453
+ "type": 4
454
+ }
455
+ return payload