ka1kuk commited on
Commit
2eb1363
0 Parent(s):

Duplicate from ka1kuk/fastapi_dummy

Browse files
Files changed (8) hide show
  1. .gitattributes +34 -0
  2. BingImageCreator.py +403 -0
  3. Dockerfile +11 -0
  4. Image.py +11 -0
  5. Linlada.py +1093 -0
  6. README.md +11 -0
  7. main.py +56 -0
  8. requirements.txt +16 -0
.gitattributes ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tflite filter=lfs diff=lfs merge=lfs -text
29
+ *.tgz filter=lfs diff=lfs merge=lfs -text
30
+ *.wasm filter=lfs diff=lfs merge=lfs -text
31
+ *.xz filter=lfs diff=lfs merge=lfs -text
32
+ *.zip filter=lfs diff=lfs merge=lfs -text
33
+ *.zst filter=lfs diff=lfs merge=lfs -text
34
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
BingImageCreator.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import asyncio
3
+ from functools import partial
4
+ import contextlib
5
+ import json
6
+ import os
7
+ import random
8
+ import sys
9
+ import time
10
+ import aiohttp
11
+ import pkg_resources
12
+ import regex
13
+ import requests
14
+ from typing import Union
15
+
16
+ if os.environ.get("BING_URL") == None:
17
+ BING_URL = "https://www.bing.com"
18
+ else:
19
+ BING_URL = os.environ.get("BING_URL")
20
+ # Generate random IP between range 13.104.0.0/14
21
+ FORWARDED_IP = (
22
+ f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
23
+ )
24
+ HEADERS = {
25
+ "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",
26
+ "accept-language": "en-US,en;q=0.9",
27
+ "cache-control": "max-age=0",
28
+ "content-type": "application/x-www-form-urlencoded",
29
+ "referrer": "https://www.bing.com/images/create/",
30
+ "origin": "https://www.bing.com",
31
+ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
32
+ "x-forwarded-for": FORWARDED_IP,
33
+ }
34
+
35
+ # Error messages
36
+ error_timeout = "Your request has timed out."
37
+ error_redirect = "Redirect failed"
38
+ error_blocked_prompt = (
39
+ "Your prompt has been blocked by Bing. Try to change any bad words and try again."
40
+ )
41
+ error_noresults = "Could not get results"
42
+ error_unsupported_lang = "\nthis language is currently not supported by bing"
43
+ error_bad_images = "Bad images"
44
+ error_no_images = "No images"
45
+ #
46
+ sending_message = "Sending request..."
47
+ wait_message = "Waiting for results..."
48
+ download_message = "\nDownloading images..."
49
+
50
+
51
+ def debug(debug_file, text_var):
52
+ """helper function for debug"""
53
+ with open(f"{debug_file}", "a", encoding="utf-8") as f:
54
+ f.write(str(text_var))
55
+
56
+
57
+ class ImageGen:
58
+ """
59
+ Image generation by Microsoft Bing
60
+ Parameters:3
61
+ auth_cookie: str
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ auth_cookie: '15iNP0L_xa8fjGOOmF9For9sfHo3dWNKCMe_7LCA8XRNqtkFx0CtlH8mSTLDTaCL7GXTYF2z_TIIJKb9C2EZa6isVYJjEK39LbaRLpMCKzb5E6zO5cNilSmlqKco6e6Hn8WIUP22j_GLYVgM1awGOejEL8lcgkN0InQjpX-STlGED3PVabcfeDgDxknaiae2L29sGJ6Mt7gZnfNfgWYuO7XFXep9HAwAzSx5cprtFbwA',
67
+ debug_file: Union[str, None] = None,
68
+ quiet: bool = False,
69
+ all_cookies: list[dict] = None,
70
+ ) -> None:
71
+ self.session: requests.Session = requests.Session()
72
+ self.session.headers = HEADERS
73
+ self.session.cookies.set("_U", auth_cookie)
74
+ if all_cookies:
75
+ for cookie in all_cookies:
76
+ self.session.cookies.set(cookie["name"], cookie["value"])
77
+ self.quiet = quiet
78
+ self.debug_file = debug_file
79
+ if self.debug_file:
80
+ self.debug = partial(debug, self.debug_file)
81
+
82
+ def get_images(self, prompt: str) -> list:
83
+ """
84
+ Fetches image links from Bing
85
+ Parameters:
86
+ prompt: str
87
+ """
88
+ if not self.quiet:
89
+ print(sending_message)
90
+ if self.debug_file:
91
+ self.debug(sending_message)
92
+ url_encoded_prompt = requests.utils.quote(prompt)
93
+ payload = f"q={url_encoded_prompt}&qs=ds"
94
+ # https://www.bing.com/images/create?q=<PROMPT>&rt=3&FORM=GENCRE
95
+ url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE"
96
+ response = self.session.post(
97
+ url, allow_redirects=False, data=payload, timeout=200
98
+ )
99
+ # check for content waring message
100
+ if "this prompt has been blocked" in response.text.lower():
101
+ if self.debug_file:
102
+ self.debug(f"ERROR: {error_blocked_prompt}")
103
+ raise Exception(
104
+ error_blocked_prompt,
105
+ )
106
+ if (
107
+ "we're working hard to offer image creator in more languages"
108
+ in response.text.lower()
109
+ ):
110
+ if self.debug_file:
111
+ self.debug(f"ERROR: {error_unsupported_lang}")
112
+ raise Exception(error_unsupported_lang)
113
+ if response.status_code != 302:
114
+ # if rt4 fails, try rt3
115
+ url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
116
+ response3 = self.session.post(url, allow_redirects=False, timeout=200)
117
+ if response3.status_code != 302:
118
+ if self.debug_file:
119
+ self.debug(f"ERROR: {error_redirect}")
120
+ print(f"ERROR: {response3.text}")
121
+ raise Exception(error_redirect)
122
+ response = response3
123
+ # Get redirect URL
124
+ redirect_url = response.headers["Location"].replace("&nfy=1", "")
125
+ request_id = redirect_url.split("id=")[-1]
126
+ self.session.get(f"{BING_URL}{redirect_url}")
127
+ # https://www.bing.com/images/create/async/results/{ID}?q={PROMPT}
128
+ polling_url = f"{BING_URL}/images/create/async/results/{request_id}?q={url_encoded_prompt}"
129
+ # Poll for results
130
+ if self.debug_file:
131
+ self.debug("Polling and waiting for result")
132
+ if not self.quiet:
133
+ print("Waiting for results...")
134
+ start_wait = time.time()
135
+ while True:
136
+ if int(time.time() - start_wait) > 200:
137
+ if self.debug_file:
138
+ self.debug(f"ERROR: {error_timeout}")
139
+ raise Exception(error_timeout)
140
+ if not self.quiet:
141
+ print(".", end="", flush=True)
142
+ response = self.session.get(polling_url)
143
+ if response.status_code != 200:
144
+ if self.debug_file:
145
+ self.debug(f"ERROR: {error_noresults}")
146
+ raise Exception(error_noresults)
147
+ if not response.text or response.text.find("errorMessage") != -1:
148
+ time.sleep(1)
149
+ continue
150
+ else:
151
+ break
152
+ # Use regex to search for src=""
153
+ image_links = regex.findall(r'src="([^"]+)"', response.text)
154
+ # Remove size limit
155
+ normal_image_links = [link.split("?w=")[0] for link in image_links]
156
+ # Remove duplicates
157
+ normal_image_links = list(set(normal_image_links))
158
+
159
+ # Bad images
160
+ bad_images = [
161
+ "https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
162
+ "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
163
+ ]
164
+ for img in normal_image_links:
165
+ if img in bad_images:
166
+ raise Exception("Bad images")
167
+ # No images
168
+ if not normal_image_links:
169
+ raise Exception(error_no_images)
170
+ return normal_image_links
171
+
172
+ def save_images(self, links: list, output_dir: str, file_name: str = None) -> None:
173
+ """
174
+ Saves images to output directory
175
+ """
176
+ if self.debug_file:
177
+ self.debug(download_message)
178
+ if not self.quiet:
179
+ print(download_message)
180
+ with contextlib.suppress(FileExistsError):
181
+ os.mkdir(output_dir)
182
+ try:
183
+ fn = f"{file_name}_" if file_name else ""
184
+ jpeg_index = 0
185
+ for link in links:
186
+ while os.path.exists(
187
+ os.path.join(output_dir, f"{fn}{jpeg_index}.jpeg")
188
+ ):
189
+ jpeg_index += 1
190
+ with self.session.get(link, stream=True) as response:
191
+ # save response to file
192
+ response.raise_for_status()
193
+ with open(
194
+ os.path.join(output_dir, f"{fn}{jpeg_index}.jpeg"), "wb"
195
+ ) as output_file:
196
+ for chunk in response.iter_content(chunk_size=8192):
197
+ output_file.write(chunk)
198
+ except requests.exceptions.MissingSchema as url_exception:
199
+ raise Exception(
200
+ "Inappropriate contents found in the generated images. Please try again or try another prompt.",
201
+ ) from url_exception
202
+
203
+
204
+ class ImageGenAsync:
205
+ """
206
+ Image generation by Microsoft Bing
207
+ Parameters:
208
+ auth_cookie: str
209
+ """
210
+
211
+ def __init__(self, auth_cookie: str, quiet: bool = False) -> None:
212
+ self.session = aiohttp.ClientSession(
213
+ headers=HEADERS,
214
+ cookies={"_U": auth_cookie},
215
+ trust_env=True,
216
+ )
217
+ self.quiet = quiet
218
+
219
+ async def __aenter__(self):
220
+ return self
221
+
222
+ async def __aexit__(self, *excinfo) -> None:
223
+ await self.session.close()
224
+
225
+ async def get_images(self, prompt: str) -> list:
226
+ """
227
+ Fetches image links from Bing
228
+ Parameters:
229
+ prompt: str
230
+ """
231
+ if not self.quiet:
232
+ print("Sending request...")
233
+ url_encoded_prompt = requests.utils.quote(prompt)
234
+ # https://www.bing.com/images/create?q=<PROMPT>&rt=3&FORM=GENCRE
235
+ url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE"
236
+ payload = f"q={url_encoded_prompt}&qs=ds"
237
+ async with self.session.post(
238
+ url, allow_redirects=False, data=payload
239
+ ) as response:
240
+ content = await response.text()
241
+ if "this prompt has been blocked" in content.lower():
242
+ raise Exception(
243
+ "Your prompt has been blocked by Bing. Try to change any bad words and try again.",
244
+ )
245
+ if response.status != 302:
246
+ # if rt4 fails, try rt3
247
+ url = (
248
+ f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
249
+ )
250
+ async with self.session.post(
251
+ url,
252
+ allow_redirects=False,
253
+ timeout=200,
254
+ ) as response3:
255
+ if response3.status != 302:
256
+ print(f"ERROR: {await response3.text()}")
257
+ raise Exception("Redirect failed")
258
+ response = response3
259
+ # Get redirect URL
260
+ redirect_url = response.headers["Location"].replace("&nfy=1", "")
261
+ request_id = redirect_url.split("id=")[-1]
262
+ await self.session.get(f"{BING_URL}{redirect_url}")
263
+ # https://www.bing.com/images/create/async/results/{ID}?q={PROMPT}
264
+ polling_url = f"{BING_URL}/images/create/async/results/{request_id}?q={url_encoded_prompt}"
265
+ # Poll for results
266
+ if not self.quiet:
267
+ print("Waiting for results...")
268
+ while True:
269
+ if not self.quiet:
270
+ print(".", end="", flush=True)
271
+ # By default, timeout is 300s, change as needed
272
+ response = await self.session.get(polling_url)
273
+ if response.status != 200:
274
+ raise Exception("Could not get results")
275
+ content = await response.text()
276
+ if content and content.find("errorMessage") == -1:
277
+ break
278
+
279
+ await asyncio.sleep(1)
280
+ continue
281
+ # Use regex to search for src=""
282
+ image_links = regex.findall(r'src="([^"]+)"', content)
283
+ # Remove size limit
284
+ normal_image_links = [link.split("?w=")[0] for link in image_links]
285
+ # Remove duplicates
286
+ normal_image_links = list(set(normal_image_links))
287
+
288
+ # Bad images
289
+ bad_images = [
290
+ "https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
291
+ "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
292
+ ]
293
+ for im in normal_image_links:
294
+ if im in bad_images:
295
+ raise Exception("Bad images")
296
+ # No images
297
+ if not normal_image_links:
298
+ raise Exception("No images")
299
+ return normal_image_links
300
+
301
+ async def save_images(self, links: list, output_dir: str) -> None:
302
+ """
303
+ Saves images to output directory
304
+ """
305
+ if not self.quiet:
306
+ print("\nDownloading images...")
307
+ with contextlib.suppress(FileExistsError):
308
+ os.mkdir(output_dir)
309
+ try:
310
+ jpeg_index = 0
311
+ for link in links:
312
+ while os.path.exists(os.path.join(output_dir, f"{jpeg_index}.jpeg")):
313
+ jpeg_index += 1
314
+ async with self.session.get(link, raise_for_status=True) as response:
315
+ # save response to file
316
+ with open(
317
+ os.path.join(output_dir, f"{jpeg_index}.jpeg"), "wb"
318
+ ) as output_file:
319
+ async for chunk in response.content.iter_chunked(8192):
320
+ output_file.write(chunk)
321
+ except aiohttp.client_exceptions.InvalidURL as url_exception:
322
+ raise Exception(
323
+ "Inappropriate contents found in the generated images. Please try again or try another prompt.",
324
+ ) from url_exception
325
+
326
+
327
+ async def async_image_gen(args) -> None:
328
+ async with ImageGenAsync(args.U, args.quiet) as image_generator:
329
+ images = await image_generator.get_images(args.prompt)
330
+ await image_generator.save_images(images, output_dir=args.output_dir)
331
+
332
+
333
+ def main():
334
+ parser = argparse.ArgumentParser()
335
+ parser.add_argument("-U", help="Auth cookie from browser", type=str)
336
+ parser.add_argument("--cookie-file", help="File containing auth cookie", type=str)
337
+ parser.add_argument(
338
+ "--prompt",
339
+ help="Prompt to generate images for",
340
+ type=str,
341
+ required=True,
342
+ )
343
+
344
+ parser.add_argument(
345
+ "--output-dir",
346
+ help="Output directory",
347
+ type=str,
348
+ default="./output",
349
+ )
350
+
351
+ parser.add_argument(
352
+ "--debug-file",
353
+ help="Path to the file where debug information will be written.",
354
+ type=str,
355
+ )
356
+
357
+ parser.add_argument(
358
+ "--quiet",
359
+ help="Disable pipeline messages",
360
+ action="store_true",
361
+ )
362
+ parser.add_argument(
363
+ "--asyncio",
364
+ help="Run ImageGen using asyncio",
365
+ action="store_true",
366
+ )
367
+ parser.add_argument(
368
+ "--version",
369
+ action="store_true",
370
+ help="Print the version number",
371
+ )
372
+
373
+ args = parser.parse_args()
374
+
375
+ if args.version:
376
+ print(pkg_resources.get_distribution("BingImageCreator").version)
377
+ sys.exit()
378
+
379
+ # Load auth cookie
380
+ cookie_json = None
381
+ if args.cookie_file is not None:
382
+ with contextlib.suppress(Exception):
383
+ with open(args.cookie_file, encoding="utf-8") as file:
384
+ cookie_json = json.load(file)
385
+
386
+ if args.U is None and args.cookie_file is None:
387
+ raise Exception("Could not find auth cookie")
388
+
389
+ if not args.asyncio:
390
+ # Create image generator
391
+ image_generator = ImageGen(
392
+ args.U, args.debug_file, args.quiet, all_cookies=cookie_json
393
+ )
394
+ image_generator.save_images(
395
+ image_generator.get_images(args.prompt),
396
+ output_dir=args.output_dir,
397
+ )
398
+ else:
399
+ asyncio.run(async_image_gen(args))
400
+
401
+
402
+ if __name__ == "__main__":
403
+ main()
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
Image.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Open pull requests and issues at https://github.com/acheong08/BingImageCreator
2
+ import .BingImageCreator
3
+
4
+ ImageGen = BingImageCreator.ImageGen
5
+
6
+ ImageGenAsync = BingImageCreator.ImageGenAsync
7
+
8
+ main = BingImageCreator.main
9
+
10
+ if __name__ == "__main__":
11
+ main()
Linlada.py ADDED
@@ -0,0 +1,1093 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ import os
7
+ import random
8
+ import re
9
+ import ssl
10
+ import sys
11
+ import time
12
+ import uuid
13
+ from enum import Enum
14
+ from pathlib import Path
15
+ from typing import Generator
16
+ from typing import Literal
17
+ from typing import Optional
18
+ from typing import Union
19
+
20
+ import aiohttp
21
+ import certifi
22
+ import httpx
23
+ from prompt_toolkit import PromptSession
24
+ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
25
+ from prompt_toolkit.completion import WordCompleter
26
+ from prompt_toolkit.history import InMemoryHistory
27
+ from prompt_toolkit.key_binding import KeyBindings
28
+ from rich.live import Live
29
+ from rich.markdown import Markdown
30
+
31
+ DELIMITER = "\x1e"
32
+
33
+
34
+ # Generate random IP between range 13.104.0.0/14
35
+ FORWARDED_IP = (
36
+ f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
37
+ )
38
+
39
+ HEADERS = {
40
+ "accept": "application/json",
41
+ "accept-language": "en-US,en;q=0.9",
42
+ "content-type": "application/json",
43
+ "sec-ch-ua": '"Not_A Brand";v="99", "Microsoft Edge";v="110", "Chromium";v="110"',
44
+ "sec-ch-ua-arch": '"x86"',
45
+ "sec-ch-ua-bitness": '"64"',
46
+ "sec-ch-ua-full-version": '"109.0.1518.78"',
47
+ "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"',
48
+ "sec-ch-ua-mobile": "?0",
49
+ "sec-ch-ua-model": "",
50
+ "sec-ch-ua-platform": '"Windows"',
51
+ "sec-ch-ua-platform-version": '"15.0.0"',
52
+ "sec-fetch-dest": "empty",
53
+ "sec-fetch-mode": "cors",
54
+ "sec-fetch-site": "same-origin",
55
+ "x-ms-client-request-id": str(uuid.uuid4()),
56
+ "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32",
57
+ "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx",
58
+ "Referrer-Policy": "origin-when-cross-origin",
59
+ "x-forwarded-for": FORWARDED_IP,
60
+ }
61
+
62
+ HEADERS_INIT_CONVER = {
63
+ "authority": "edgeservices.bing.com",
64
+ "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",
65
+ "accept-language": "en-US,en;q=0.9",
66
+ "cache-control": "max-age=0",
67
+ "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
68
+ "sec-ch-ua-arch": '"x86"',
69
+ "sec-ch-ua-bitness": '"64"',
70
+ "sec-ch-ua-full-version": '"110.0.1587.69"',
71
+ "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"',
72
+ "sec-ch-ua-mobile": "?0",
73
+ "sec-ch-ua-model": '""',
74
+ "sec-ch-ua-platform": '"Windows"',
75
+ "sec-ch-ua-platform-version": '"15.0.0"',
76
+ "sec-fetch-dest": "document",
77
+ "sec-fetch-mode": "navigate",
78
+ "sec-fetch-site": "none",
79
+ "sec-fetch-user": "?1",
80
+ "upgrade-insecure-requests": "1",
81
+ "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",
82
+ "x-edge-shopping-flag": "1",
83
+ "x-forwarded-for": FORWARDED_IP,
84
+ }
85
+
86
+ ssl_context = ssl.create_default_context()
87
+ ssl_context.load_verify_locations(certifi.where())
88
+
89
+
90
+ class NotAllowedToAccess(Exception):
91
+ pass
92
+
93
+
94
+ class ConversationStyle(Enum):
95
+ creative = [
96
+ "nlu_direct_response_filter",
97
+ "deepleo",
98
+ "disable_emoji_spoken_text",
99
+ "responsible_ai_policy_235",
100
+ "enablemm",
101
+ "h3imaginative",
102
+ "travelansgnd",
103
+ "dv3sugg",
104
+ "clgalileo",
105
+ "gencontentv3",
106
+ "dv3sugg",
107
+ "responseos",
108
+ "e2ecachewrite",
109
+ "cachewriteext",
110
+ "nodlcpcwrite",
111
+ "travelansgnd",
112
+ "nojbfedge",
113
+ ]
114
+ balanced = [
115
+ "nlu_direct_response_filter",
116
+ "deepleo",
117
+ "disable_emoji_spoken_text",
118
+ "responsible_ai_policy_235",
119
+ "enablemm",
120
+ "galileo",
121
+ "dv3sugg",
122
+ "responseos",
123
+ "e2ecachewrite",
124
+ "cachewriteext",
125
+ "nodlcpcwrite",
126
+ "travelansgnd",
127
+ "nojbfedge",
128
+ ]
129
+ precise = [
130
+ "nlu_direct_response_filter",
131
+ "deepleo",
132
+ "disable_emoji_spoken_text",
133
+ "responsible_ai_policy_235",
134
+ "enablemm",
135
+ "galileo",
136
+ "dv3sugg",
137
+ "responseos",
138
+ "e2ecachewrite",
139
+ "cachewriteext",
140
+ "nodlcpcwrite",
141
+ "travelansgnd",
142
+ "h3precise",
143
+ "clgalileo",
144
+ "nojbfedge",
145
+ ]
146
+
147
+
148
+ CONVERSATION_STYLE_TYPE = Optional[
149
+ Union[ConversationStyle, Literal["creative", "balanced", "precise"]]
150
+ ]
151
+
152
+
153
+ def _append_identifier(msg: dict) -> str:
154
+ """
155
+ Appends special character to end of message to identify end of message
156
+ """
157
+ # Convert dict to json string
158
+ return json.dumps(msg, ensure_ascii=False) + DELIMITER
159
+
160
+
161
+ def _get_ran_hex(length: int = 32) -> str:
162
+ """
163
+ Returns random hex string
164
+ """
165
+ return "".join(random.choice("0123456789abcdef") for _ in range(length))
166
+
167
+
168
+ class _ChatHubRequest:
169
+ """
170
+ Request object for ChatHub
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ conversation_signature: str,
176
+ client_id: str,
177
+ conversation_id: str,
178
+ invocation_id: int = 0,
179
+ ) -> None:
180
+ self.struct: dict = {}
181
+
182
+ self.client_id: str = client_id
183
+ self.conversation_id: str = conversation_id
184
+ self.conversation_signature: str = conversation_signature
185
+ self.invocation_id: int = invocation_id
186
+
187
+ def update(
188
+ self,
189
+ prompt: str,
190
+ conversation_style: CONVERSATION_STYLE_TYPE,
191
+ options: list | None = None,
192
+ webpage_context: str | None = None,
193
+ search_result: bool = False,
194
+ ) -> None:
195
+ """
196
+ Updates request object
197
+ """
198
+ if options is None:
199
+ options = [
200
+ "deepleo",
201
+ "enable_debug_commands",
202
+ "disable_emoji_spoken_text",
203
+ "enablemm",
204
+ ]
205
+ if conversation_style:
206
+ if not isinstance(conversation_style, ConversationStyle):
207
+ conversation_style = getattr(ConversationStyle, conversation_style)
208
+ options = conversation_style.value
209
+ self.struct = {
210
+ "arguments": [
211
+ {
212
+ "source": "cib",
213
+ "optionsSets": options,
214
+ "allowedMessageTypes": [
215
+ "Chat",
216
+ "Disengaged",
217
+ "AdsQuery",
218
+ "SemanticSerp",
219
+ "GenerateContentQuery",
220
+ "SearchQuery",
221
+ ],
222
+ "sliceIds": [
223
+ "chk1cf",
224
+ "nopreloadsscf",
225
+ "winlongmsg2tf",
226
+ "perfimpcomb",
227
+ "sugdivdis",
228
+ "sydnoinputt",
229
+ "wpcssopt",
230
+ "wintone2tf",
231
+ "0404sydicnbs0",
232
+ "405suggbs0",
233
+ "scctl",
234
+ "330uaugs0",
235
+ "0329resp",
236
+ "udscahrfon",
237
+ "udstrblm5",
238
+ "404e2ewrt",
239
+ "408nodedups0",
240
+ "403tvlansgnd",
241
+ ],
242
+ "traceId": _get_ran_hex(32),
243
+ "isStartOfSession": self.invocation_id == 0,
244
+ "message": {
245
+ "author": "user",
246
+ "inputMethod": "Keyboard",
247
+ "text": prompt,
248
+ "messageType": "Chat",
249
+ },
250
+ "conversationSignature": self.conversation_signature,
251
+ "participant": {
252
+ "id": self.client_id,
253
+ },
254
+ "conversationId": self.conversation_id,
255
+ },
256
+ ],
257
+ "invocationId": str(self.invocation_id),
258
+ "target": "chat",
259
+ "type": 4,
260
+ }
261
+ if search_result:
262
+ have_search_result = [
263
+ "InternalSearchQuery",
264
+ "InternalSearchResult",
265
+ "InternalLoaderMessage",
266
+ "RenderCardRequest",
267
+ ]
268
+ self.struct["arguments"][0]["allowedMessageTypes"] += have_search_result
269
+ if webpage_context:
270
+ self.struct["arguments"][0]["previousMessages"] = [
271
+ {
272
+ "author": "user",
273
+ "description": webpage_context,
274
+ "contextType": "WebPage",
275
+ "messageType": "Context",
276
+ "messageId": "discover-web--page-ping-mriduna-----",
277
+ },
278
+ ]
279
+ self.invocation_id += 1
280
+
281
+
282
+ class _Conversation:
283
+ """
284
+ Conversation API
285
+ """
286
+
287
+ def __init__(
288
+ self,
289
+ proxy: str | None = None,
290
+ async_mode: bool = False,
291
+ cookies: list[dict] | None = None,
292
+ ) -> None:
293
+ if async_mode:
294
+ return
295
+ self.struct: dict = {
296
+ "conversationId": None,
297
+ "clientId": None,
298
+ "conversationSignature": None,
299
+ "result": {"value": "Success", "message": None},
300
+ }
301
+ self.proxy = proxy
302
+ proxy = (
303
+ proxy
304
+ or os.environ.get("all_proxy")
305
+ or os.environ.get("ALL_PROXY")
306
+ or os.environ.get("https_proxy")
307
+ or os.environ.get("HTTPS_PROXY")
308
+ or None
309
+ )
310
+ if proxy is not None and proxy.startswith("socks5h://"):
311
+ proxy = "socks5://" + proxy[len("socks5h://") :]
312
+ self.session = httpx.Client(
313
+ proxies=proxy,
314
+ timeout=30,
315
+ headers=HEADERS_INIT_CONVER,
316
+ )
317
+ if cookies:
318
+ for cookie in cookies:
319
+ self.session.cookies.set(cookie["name"], cookie["value"])
320
+ # Send GET request
321
+ response = self.session.get(
322
+ url=os.environ.get("BING_PROXY_URL")
323
+ or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
324
+ )
325
+ if response.status_code != 200:
326
+ response = self.session.get(
327
+ "https://edge.churchless.tech/edgesvc/turing/conversation/create",
328
+ )
329
+ if response.status_code != 200:
330
+ print(f"Status code: {response.status_code}")
331
+ print(response.text)
332
+ print(response.url)
333
+ raise Exception("Authentication failed")
334
+ try:
335
+ self.struct = response.json()
336
+ except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
337
+ raise Exception(
338
+ "Authentication failed. You have not been accepted into the beta.",
339
+ ) from exc
340
+ if self.struct["result"]["value"] == "UnauthorizedRequest":
341
+ raise NotAllowedToAccess(self.struct["result"]["message"])
342
+
343
+ @staticmethod
344
+ async def create(
345
+ proxy: str | None = None,
346
+ cookies: list[dict] | None = None,
347
+ ) -> _Conversation:
348
+ self = _Conversation(async_mode=True)
349
+ self.struct = {
350
+ "conversationId": None,
351
+ "clientId": None,
352
+ "conversationSignature": None,
353
+ "result": {"value": "Success", "message": None},
354
+ }
355
+ self.proxy = proxy
356
+ proxy = (
357
+ proxy
358
+ or os.environ.get("all_proxy")
359
+ or os.environ.get("ALL_PROXY")
360
+ or os.environ.get("https_proxy")
361
+ or os.environ.get("HTTPS_PROXY")
362
+ or None
363
+ )
364
+ if proxy is not None and proxy.startswith("socks5h://"):
365
+ proxy = "socks5://" + proxy[len("socks5h://") :]
366
+ transport = httpx.AsyncHTTPTransport(retries=10)
367
+ # Convert cookie format to httpx format
368
+ formatted_cookies = None
369
+ if cookies:
370
+ formatted_cookies = httpx.Cookies()
371
+ for cookie in cookies:
372
+ formatted_cookies.set(cookie["name"], cookie["value"])
373
+ async with httpx.AsyncClient(
374
+ proxies=proxy,
375
+ timeout=30,
376
+ headers=HEADERS_INIT_CONVER,
377
+ transport=transport,
378
+ cookies=formatted_cookies,
379
+ ) as client:
380
+ # Send GET request
381
+ response = await client.get(
382
+ url=os.environ.get("BING_PROXY_URL")
383
+ or "https://edgeservices.bing.com/edgesvc/turing/conversation/create",
384
+ )
385
+ if response.status_code != 200:
386
+ response = await client.get(
387
+ "https://edge.churchless.tech/edgesvc/turing/conversation/create",
388
+ )
389
+ if response.status_code != 200:
390
+ print(f"Status code: {response.status_code}")
391
+ print(response.text)
392
+ print(response.url)
393
+ raise Exception("Authentication failed")
394
+ try:
395
+ self.struct = response.json()
396
+ except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
397
+ raise Exception(
398
+ "Authentication failed. You have not been accepted into the beta.",
399
+ ) from exc
400
+ if self.struct["result"]["value"] == "UnauthorizedRequest":
401
+ raise NotAllowedToAccess(self.struct["result"]["message"])
402
+ return self
403
+
404
+
405
+ class _ChatHub:
406
+ """
407
+ Chat API
408
+ """
409
+
410
+ def __init__(
411
+ self,
412
+ conversation: _Conversation,
413
+ proxy: str = None,
414
+ cookies: list[dict] | None = None,
415
+ ) -> None:
416
+ self.session: aiohttp.ClientSession | None = None
417
+ self.wss: aiohttp.ClientWebSocketResponse | None = None
418
+ self.request: _ChatHubRequest
419
+ self.loop: bool
420
+ self.task: asyncio.Task
421
+ self.request = _ChatHubRequest(
422
+ conversation_signature=conversation.struct["conversationSignature"],
423
+ client_id=conversation.struct["clientId"],
424
+ conversation_id=conversation.struct["conversationId"],
425
+ )
426
+ self.cookies = cookies
427
+ self.proxy: str = proxy
428
+
429
+ async def ask_stream(
430
+ self,
431
+ prompt: str,
432
+ wss_link: str,
433
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
434
+ raw: bool = False,
435
+ options: dict = None,
436
+ webpage_context: str | None = None,
437
+ search_result: bool = False,
438
+ ) -> Generator[str, None, None]:
439
+ """
440
+ Ask a question to the bot
441
+ """
442
+ timeout = aiohttp.ClientTimeout(total=30)
443
+ self.session = aiohttp.ClientSession(timeout=timeout)
444
+
445
+ if self.wss and not self.wss.closed:
446
+ await self.wss.close()
447
+ # Check if websocket is closed
448
+ self.wss = await self.session.ws_connect(
449
+ wss_link,
450
+ headers=HEADERS,
451
+ ssl=ssl_context,
452
+ proxy=self.proxy,
453
+ autoping=False,
454
+ )
455
+ await self._initial_handshake()
456
+ if self.request.invocation_id == 0:
457
+ # Construct a ChatHub request
458
+ self.request.update(
459
+ prompt=prompt,
460
+ conversation_style=conversation_style,
461
+ options=options,
462
+ webpage_context=webpage_context,
463
+ search_result=search_result,
464
+ )
465
+ else:
466
+ async with httpx.AsyncClient() as client:
467
+ response = await client.post(
468
+ "https://sydney.bing.com/sydney/UpdateConversation/",
469
+ json={
470
+ "messages": [
471
+ {
472
+ "author": "user",
473
+ "description": webpage_context,
474
+ "contextType": "WebPage",
475
+ "messageType": "Context",
476
+ },
477
+ ],
478
+ "conversationId": self.request.conversation_id,
479
+ "source": "cib",
480
+ "traceId": _get_ran_hex(32),
481
+ "participant": {"id": self.request.client_id},
482
+ "conversationSignature": self.request.conversation_signature,
483
+ },
484
+ )
485
+ if response.status_code != 200:
486
+ print(f"Status code: {response.status_code}")
487
+ print(response.text)
488
+ print(response.url)
489
+ raise Exception("Update web page context failed")
490
+ # Construct a ChatHub request
491
+ self.request.update(
492
+ prompt=prompt,
493
+ conversation_style=conversation_style,
494
+ options=options,
495
+ )
496
+ # Send request
497
+ await self.wss.send_str(_append_identifier(self.request.struct))
498
+ final = False
499
+ draw = False
500
+ resp_txt = ""
501
+ result_text = ""
502
+ resp_txt_no_link = ""
503
+ while not final:
504
+ msg = await self.wss.receive()
505
+ objects = msg.data.split(DELIMITER)
506
+ for obj in objects:
507
+ if obj is None or not obj:
508
+ continue
509
+ response = json.loads(obj)
510
+ if response.get("type") != 2 and raw:
511
+ yield False, response
512
+ elif response.get("type") == 1 and response["arguments"][0].get(
513
+ "messages",
514
+ ):
515
+ if not draw:
516
+ if (
517
+ response["arguments"][0]["messages"][0].get("messageType")
518
+ == "GenerateContentQuery"
519
+ ):
520
+ draw = True
521
+ if (
522
+ response["arguments"][0]["messages"][0]["contentOrigin"]
523
+ != "Apology"
524
+ ) and not draw:
525
+ resp_txt = result_text + response["arguments"][0][
526
+ "messages"
527
+ ][0]["adaptiveCards"][0]["body"][0].get("text", "")
528
+ resp_txt_no_link = result_text + response["arguments"][0][
529
+ "messages"
530
+ ][0].get("text", "")
531
+ if response["arguments"][0]["messages"][0].get(
532
+ "messageType",
533
+ ):
534
+ resp_txt = (
535
+ resp_txt
536
+ + response["arguments"][0]["messages"][0][
537
+ "adaptiveCards"
538
+ ][0]["body"][0]["inlines"][0].get("text")
539
+ + "\n"
540
+ )
541
+ result_text = (
542
+ result_text
543
+ + response["arguments"][0]["messages"][0][
544
+ "adaptiveCards"
545
+ ][0]["body"][0]["inlines"][0].get("text")
546
+ + "\n"
547
+ )
548
+ yield False, resp_txt
549
+
550
+ elif response.get("type") == 2:
551
+ if response["item"]["result"].get("error"):
552
+ await self.close()
553
+ raise Exception(
554
+ f"{response['item']['result']['value']}: {response['item']['result']['message']}",
555
+ )
556
+ if draw:
557
+ cache = response["item"]["messages"][1]["adaptiveCards"][0][
558
+ "body"
559
+ ][0]["text"]
560
+ response["item"]["messages"][1]["adaptiveCards"][0]["body"][0][
561
+ "text"
562
+ ] = (cache + resp_txt)
563
+ if (
564
+ response["item"]["messages"][-1]["contentOrigin"] == "Apology"
565
+ and resp_txt
566
+ ):
567
+ response["item"]["messages"][-1]["text"] = resp_txt_no_link
568
+ response["item"]["messages"][-1]["adaptiveCards"][0]["body"][0][
569
+ "text"
570
+ ] = resp_txt
571
+ print(
572
+ "Preserved the message from being deleted",
573
+ file=sys.stderr,
574
+ )
575
+ final = True
576
+ await self.close()
577
+ yield True, response
578
+
579
+ async def _initial_handshake(self) -> None:
580
+ await self.wss.send_str(_append_identifier({"protocol": "json", "version": 1}))
581
+ await self.wss.receive()
582
+
583
+ async def close(self) -> None:
584
+ """
585
+ Close the connection
586
+ """
587
+ if self.wss and not self.wss.closed:
588
+ await self.wss.close()
589
+ if self.session and not self.session.closed:
590
+ await self.session.close()
591
+
592
+
593
+ class Chatbot:
594
+ """
595
+ Combines everything to make it seamless
596
+ """
597
+
598
+ def __init__(
599
+ self,
600
+ proxy: str | None = None,
601
+ cookies: list[dict] | None = None,
602
+ ) -> None:
603
+ self.proxy: str | None = proxy
604
+ self.chat_hub: _ChatHub = _ChatHub(
605
+ _Conversation(self.proxy, cookies=cookies),
606
+ proxy=self.proxy,
607
+ cookies=cookies,
608
+ )
609
+
610
+ @staticmethod
611
+ async def create(
612
+ proxy: str | None = None,
613
+ cookies: list[dict] | None = None,
614
+ ):
615
+ self = Chatbot.__new__(Chatbot)
616
+ self.proxy = proxy
617
+ self.chat_hub = _ChatHub(
618
+ await _Conversation.create(self.proxy, cookies=cookies),
619
+ proxy=self.proxy,
620
+ cookies=cookies,
621
+ )
622
+ return self
623
+
624
+ async def ask(
625
+ self,
626
+ prompt: str,
627
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
628
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
629
+ options: dict = None,
630
+ webpage_context: str | None = None,
631
+ search_result: bool = False,
632
+ ) -> dict:
633
+ """
634
+ Ask a question to the bot
635
+ """
636
+ async for final, response in self.chat_hub.ask_stream(
637
+ prompt=prompt,
638
+ conversation_style=conversation_style,
639
+ wss_link=wss_link,
640
+ options=options,
641
+ webpage_context=webpage_context,
642
+ search_result=search_result,
643
+ ):
644
+ if final:
645
+ return response
646
+ await self.chat_hub.wss.close()
647
+ return {}
648
+
649
+ async def ask_stream(
650
+ self,
651
+ prompt: str,
652
+ wss_link: str = "wss://sydney.bing.com/sydney/ChatHub",
653
+ conversation_style: CONVERSATION_STYLE_TYPE = None,
654
+ raw: bool = False,
655
+ options: dict = None,
656
+ webpage_context: str | None = None,
657
+ search_result: bool = False,
658
+ ) -> Generator[str, None, None]:
659
+ """
660
+ Ask a question to the bot
661
+ """
662
+ async for response in self.chat_hub.ask_stream(
663
+ prompt=prompt,
664
+ conversation_style=conversation_style,
665
+ wss_link=wss_link,
666
+ raw=raw,
667
+ options=options,
668
+ webpage_context=webpage_context,
669
+ search_result=search_result,
670
+ ):
671
+ yield response
672
+
673
+ async def close(self) -> None:
674
+ """
675
+ Close the connection
676
+ """
677
+ await self.chat_hub.close()
678
+
679
+ async def reset(self) -> None:
680
+ """
681
+ Reset the conversation
682
+ """
683
+ await self.close()
684
+ self.chat_hub = _ChatHub(
685
+ await _Conversation.create(self.proxy),
686
+ proxy=self.proxy,
687
+ )
688
+
689
+
690
+ async def _get_input_async(
691
+ session: PromptSession = None,
692
+ completer: WordCompleter = None,
693
+ ) -> str:
694
+ """
695
+ Multiline input function.
696
+ """
697
+ return await session.prompt_async(
698
+ completer=completer,
699
+ multiline=True,
700
+ auto_suggest=AutoSuggestFromHistory(),
701
+ )
702
+
703
+
704
+ def _create_session() -> PromptSession:
705
+ kb = KeyBindings()
706
+
707
+ @kb.add("enter")
708
+ def _(event):
709
+ buffer_text = event.current_buffer.text
710
+ if buffer_text.startswith("!"):
711
+ event.current_buffer.validate_and_handle()
712
+ else:
713
+ event.current_buffer.insert_text("\n")
714
+
715
+ @kb.add("escape")
716
+ def _(event):
717
+ if event.current_buffer.complete_state:
718
+ # event.current_buffer.cancel_completion()
719
+ event.current_buffer.text = ""
720
+
721
+ return PromptSession(key_bindings=kb, history=InMemoryHistory())
722
+
723
+
724
+ def _create_completer(commands: list, pattern_str: str = "$"):
725
+ return WordCompleter(words=commands, pattern=re.compile(pattern_str))
726
+
727
+
728
+ async def async_main(args: argparse.Namespace) -> None:
729
+ """
730
+ Main function
731
+ """
732
+ print("Initializing...")
733
+ print("Enter `alt+enter` or `escape+enter` to send a message")
734
+ # Read and parse cookies
735
+ cookies = None
736
+ if args.cookie_file:
737
+ cookies = json.loads(open(args.cookie_file, encoding="utf-8").read())
738
+ bot = await Chatbot.create(proxy=args.proxy, cookies=cookies)
739
+ session = _create_session()
740
+ completer = _create_completer(["!help", "!exit", "!reset"])
741
+ initial_prompt = args.prompt
742
+
743
+ while True:
744
+ print("\nYou:")
745
+ if initial_prompt:
746
+ question = initial_prompt
747
+ print(question)
748
+ initial_prompt = None
749
+ else:
750
+ question = (
751
+ input()
752
+ if args.enter_once
753
+ else await _get_input_async(session=session, completer=completer)
754
+ )
755
+ print()
756
+ if question == "!exit":
757
+ break
758
+ if question == "!help":
759
+ print(
760
+ """
761
+ !help - Show this help message
762
+ !exit - Exit the program
763
+ !reset - Reset the conversation
764
+ """,
765
+ )
766
+ continue
767
+ if question == "!reset":
768
+ await bot.reset()
769
+ continue
770
+ print("Bot:")
771
+ if args.no_stream:
772
+ print(
773
+ (
774
+ await bot.ask(
775
+ prompt=question,
776
+ conversation_style=args.style,
777
+ wss_link=args.wss_link,
778
+ )
779
+ )["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"],
780
+ )
781
+ else:
782
+ wrote = 0
783
+ if args.rich:
784
+ md = Markdown("")
785
+ with Live(md, auto_refresh=False) as live:
786
+ async for final, response in bot.ask_stream(
787
+ prompt=question,
788
+ conversation_style=args.style,
789
+ wss_link=args.wss_link,
790
+ ):
791
+ if not final:
792
+ if wrote > len(response):
793
+ print(md)
794
+ print(Markdown("***Bing revoked the response.***"))
795
+ wrote = len(response)
796
+ md = Markdown(response)
797
+ live.update(md, refresh=True)
798
+ else:
799
+ async for final, response in bot.ask_stream(
800
+ prompt=question,
801
+ conversation_style=args.style,
802
+ wss_link=args.wss_link,
803
+ ):
804
+ if not final:
805
+ if not wrote:
806
+ print(response, end="", flush=True)
807
+ else:
808
+ print(response[wrote:], end="", flush=True)
809
+ wrote = len(response)
810
+ print()
811
+ await bot.close()
812
+
813
+
814
+ def main() -> None:
815
+ print(
816
+ """
817
+ EdgeGPT - A demo of reverse engineering the Bing GPT chatbot
818
+ Repo: github.com/acheong08/EdgeGPT
819
+ By: Antonio Cheong
820
+
821
+ !help for help
822
+
823
+ Type !exit to exit
824
+ """,
825
+ )
826
+ parser = argparse.ArgumentParser()
827
+ parser.add_argument("--enter-once", action="store_true")
828
+ parser.add_argument("--no-stream", action="store_true")
829
+ parser.add_argument("--rich", action="store_true")
830
+ parser.add_argument(
831
+ "--proxy",
832
+ help="Proxy URL (e.g. socks5://127.0.0.1:1080)",
833
+ type=str,
834
+ )
835
+ parser.add_argument(
836
+ "--wss-link",
837
+ help="WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)",
838
+ type=str,
839
+ default="wss://sydney.bing.com/sydney/ChatHub",
840
+ )
841
+ parser.add_argument(
842
+ "--style",
843
+ choices=["creative", "balanced", "precise"],
844
+ default="balanced",
845
+ )
846
+ parser.add_argument(
847
+ "--prompt",
848
+ type=str,
849
+ default="",
850
+ required=False,
851
+ help="prompt to start with",
852
+ )
853
+ parser.add_argument(
854
+ "--cookie-file",
855
+ type=str,
856
+ default="",
857
+ required=False,
858
+ help="path to cookie file",
859
+ )
860
+ args = parser.parse_args()
861
+ asyncio.run(async_main(args))
862
+
863
+
864
+ class Cookie:
865
+ """
866
+ Convenience class for Bing Cookie files, data, and configuration. This Class
867
+ is updated dynamically by the Query class to allow cycling through >1
868
+ cookie/credentials file e.g. when daily request limits (current 200 per
869
+ account per day) are exceeded.
870
+ """
871
+
872
+ current_file_index = 0
873
+ dirpath = Path("./").resolve()
874
+ search_pattern = "bing_cookies_*.json"
875
+ ignore_files = set()
876
+
877
+ @classmethod
878
+ def fetch_default(cls, path=None):
879
+ from selenium import webdriver
880
+ from selenium.webdriver.common.by import By
881
+
882
+ driver = webdriver.Edge()
883
+ driver.get("https://bing.com/chat")
884
+ time.sleep(5)
885
+ xpath = '//button[@id="bnp_btn_accept"]'
886
+ driver.find_element(By.XPATH, xpath).click()
887
+ time.sleep(2)
888
+ xpath = '//a[@id="codexPrimaryButton"]'
889
+ driver.find_element(By.XPATH, xpath).click()
890
+ if path is None:
891
+ path = Path("./bing_cookies__default.json")
892
+ # Double underscore ensures this file is first when sorted
893
+ cookies = driver.get_cookies()
894
+ Path(path).write_text(json.dumps(cookies, indent=4), encoding="utf-8")
895
+ # Path again in case supplied path is: str
896
+ print(f"Cookies saved to: {path}")
897
+ driver.quit()
898
+
899
+ @classmethod
900
+ def files(cls):
901
+ """Return a sorted list of all cookie files matching .search_pattern"""
902
+ all_files = set(cls.dirpath.glob(cls.search_pattern))
903
+ return sorted(list(all_files - cls.ignore_files))
904
+
905
+ @classmethod
906
+ def import_data(cls):
907
+ """
908
+ Read the active cookie file and populate the following attributes:
909
+
910
+ .current_filepath
911
+ .current_data
912
+ .image_token
913
+ """
914
+ try:
915
+ cls.current_filepath = cls.files()[cls.current_file_index]
916
+ except IndexError:
917
+ print(
918
+ "> Please set Cookie.current_filepath to a valid cookie file, then run Cookie.import_data()",
919
+ )
920
+ return
921
+ print(f"> Importing cookies from: {cls.current_filepath.name}")
922
+ with open(cls.current_filepath, encoding="utf-8") as file:
923
+ cls.current_data = json.load(file)
924
+ cls.image_token = [x for x in cls.current_data if x.get("name") == "_U"]
925
+ cls.image_token = cls.image_token[0].get("value")
926
+
927
+ @classmethod
928
+ def import_next(cls):
929
+ """
930
+ Cycle through to the next cookies file. Import it. Mark the previous
931
+ file to be ignored for the remainder of the current session.
932
+ """
933
+ cls.ignore_files.add(cls.current_filepath)
934
+ if Cookie.current_file_index >= len(cls.files()):
935
+ Cookie.current_file_index = 0
936
+ Cookie.import_data()
937
+
938
+
939
+ class Query:
940
+ """
941
+ A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input,
942
+ config, and output all together. Relies on Cookie class for authentication
943
+ """
944
+
945
+ def __init__(
946
+ self,
947
+ prompt,
948
+ style="precise",
949
+ content_type="text",
950
+ cookie_file=0,
951
+ echo=True,
952
+ echo_prompt=False,
953
+ ):
954
+ """
955
+ Arguments:
956
+
957
+ prompt: Text to enter into Bing Chat
958
+ style: creative, balanced, or precise
959
+ content_type: "text" for Bing Chat; "image" for Dall-e
960
+ cookie_file: Path, filepath string, or index (int) to list of cookie paths
961
+ echo: Print something to confirm request made
962
+ echo_prompt: Print confirmation of the evaluated prompt
963
+ """
964
+ self.index = []
965
+ self.request_count = {}
966
+ self.image_dirpath = Path("./").resolve()
967
+ Cookie.import_data()
968
+ self.index += [self]
969
+ self.prompt = prompt
970
+ files = Cookie.files()
971
+ if isinstance(cookie_file, int):
972
+ index = cookie_file if cookie_file < len(files) else 0
973
+ else:
974
+ if not isinstance(cookie_file, (str, Path)):
975
+ message = "'cookie_file' must be an int, str, or Path object"
976
+ raise TypeError(message)
977
+ cookie_file = Path(cookie_file)
978
+ if cookie_file in files(): # Supplied filepath IS in Cookie.dirpath
979
+ index = files.index(cookie_file)
980
+ else: # Supplied filepath is NOT in Cookie.dirpath
981
+ if cookie_file.is_file():
982
+ Cookie.dirpath = cookie_file.parent.resolve()
983
+ if cookie_file.is_dir():
984
+ Cookie.dirpath = cookie_file.resolve()
985
+ index = 0
986
+ Cookie.current_file_index = index
987
+ if content_type == "text":
988
+ self.style = style
989
+ self.log_and_send_query(echo, echo_prompt)
990
+ if content_type == "image":
991
+ self.create_image()
992
+
993
+ def log_and_send_query(self, echo, echo_prompt):
994
+ self.response = asyncio.run(self.send_to_bing(echo, echo_prompt))
995
+ name = str(Cookie.current_filepath.name)
996
+ if not self.request_count.get(name):
997
+ self.request_count[name] = 1
998
+ else:
999
+ self.request_count[name] += 1
1000
+
1001
+ def create_image(self):
1002
+ print('Image cannot available!')
1003
+
1004
+ async def send_to_bing(self, echo=True, echo_prompt=False):
1005
+ """Creat, submit, then close a Chatbot instance. Return the response"""
1006
+ retries = len(Cookie.files())
1007
+ while retries:
1008
+ try:
1009
+ bot = await Chatbot.create()
1010
+ if echo_prompt:
1011
+ print(f"> {self.prompt=}")
1012
+ if echo:
1013
+ print("> Waiting for response...")
1014
+ if self.style.lower() not in "creative balanced precise".split():
1015
+ self.style = "precise"
1016
+ response = await bot.ask(
1017
+ prompt=self.prompt,
1018
+ conversation_style=getattr(ConversationStyle, self.style),
1019
+ # wss_link="wss://sydney.bing.com/sydney/ChatHub"
1020
+ # What other values can this parameter take? It seems to be optional
1021
+ )
1022
+ return response
1023
+ except KeyError:
1024
+ print(
1025
+ f"> KeyError [{Cookie.current_filepath.name} may have exceeded the daily limit]",
1026
+ )
1027
+ Cookie.import_next()
1028
+ retries -= 1
1029
+ finally:
1030
+ await bot.close()
1031
+
1032
+ @property
1033
+ def output(self):
1034
+ """The response from a completed Chatbot request"""
1035
+ return self.response["item"]["messages"][1]["text"]
1036
+
1037
+ @property
1038
+ def sources(self):
1039
+ """The source names and details parsed from a completed Chatbot request"""
1040
+ return self.response["item"]["messages"][1]["sourceAttributions"]
1041
+
1042
+ @property
1043
+ def sources_dict(self):
1044
+ """The source names and details as a dictionary"""
1045
+ sources_dict = {}
1046
+ name = "providerDisplayName"
1047
+ url = "seeMoreUrl"
1048
+ for source in self.sources:
1049
+ if name in source.keys() and url in source.keys():
1050
+ sources_dict[source[name]] = source[url]
1051
+ else:
1052
+ continue
1053
+ return sources_dict
1054
+
1055
+ @property
1056
+ def code(self):
1057
+ """Extract and join any snippets of Python code in the response"""
1058
+ code_blocks = self.output.split("```")[1:-1:2]
1059
+ code_blocks = ["\n".join(x.splitlines()[1:]) for x in code_blocks]
1060
+ return "\n\n".join(code_blocks)
1061
+
1062
+ @property
1063
+ def languages(self):
1064
+ """Extract all programming languages given in code blocks"""
1065
+ code_blocks = self.output.split("```")[1:-1:2]
1066
+ return {x.splitlines()[0] for x in code_blocks}
1067
+
1068
+ @property
1069
+ def suggestions(self):
1070
+ """Follow-on questions suggested by the Chatbot"""
1071
+ return [
1072
+ x["text"]
1073
+ for x in self.response["item"]["messages"][1]["suggestedResponses"]
1074
+ ]
1075
+
1076
+ def __repr__(self):
1077
+ return f"<EdgeGPT.Query: {self.prompt}>"
1078
+
1079
+ def __str__(self):
1080
+ return self.output
1081
+
1082
+
1083
+ class ImageQuery(Query):
1084
+ def __init__(self, prompt, **kwargs):
1085
+ kwargs.update({"content_type": "image"})
1086
+ super().__init__(prompt, **kwargs)
1087
+
1088
+ def __repr__(self):
1089
+ return f"<EdgeGPT.ImageQuery: {self.prompt}>"
1090
+
1091
+
1092
+ if __name__ == "__main__":
1093
+ main()
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Fastapi Dummy
3
+ emoji: 🐢
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ duplicated_from: ka1kuk/fastapi_dummy
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
main.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import asyncio
4
+ from Linlada import Chatbot, ConversationStyle
5
+ from diffusers import StableDiffusionPipeline, EulerDiscreteScheduler
6
+ import torch
7
+
8
+ app = FastAPI()
9
+
10
+ app.add_middleware(
11
+ CORSMiddleware,
12
+ allow_origins=["*"],
13
+ allow_methods=["*"],
14
+ allow_headers=["*"],
15
+ allow_credentials=True,
16
+ )
17
+
18
+ async def generate(prompt):
19
+ bot = await Chatbot.create()
20
+ result = await bot.ask(prompt=prompt, conversation_style=ConversationStyle.precise)
21
+ return result
22
+
23
+ def dummy(images, **kwargs):
24
+ return images, False
25
+
26
+ async def generate_image(prompt):
27
+ model_id = "runwayml/stable-diffusion-v1-5"
28
+ pipe = await StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
29
+ pipe = pipe.to("cuda")
30
+ pipe.safety_checker = dummy
31
+ image = await pipe(prompt).images[0]
32
+ return image
33
+
34
+ @app.get("/")
35
+ def read_root():
36
+ return "Hello, I'm Linlada"
37
+
38
+ @app.get("/test/{hello}")
39
+ def hi(hello: str):
40
+ return {"text": hello}
41
+
42
+ @app.get("/image/{image}")
43
+ def img(image: str):
44
+ loop = asyncio.new_event_loop()
45
+ asyncio.set_event_loop(loop)
46
+ result = loop.run_until_complete(generate_image(image))
47
+ loop.close()
48
+ return result
49
+
50
+ @app.get('/linlada/{prompt}')
51
+ def generate_image_route(prompt: str):
52
+ loop = asyncio.new_event_loop()
53
+ asyncio.set_event_loop(loop)
54
+ result = loop.run_until_complete(generate(prompt))
55
+ loop.close()
56
+ return result
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.74.*
2
+ requests==2.27.*
3
+ sentencepiece==0.1.*
4
+ torch==1.11.*
5
+ transformers==4.*
6
+ uvicorn[standard]==0.17.*
7
+ aiohttp == 3.8.*
8
+ certifi == 2023.5.*
9
+ httpx == 0.24.*
10
+ prompt-toolkit == 3.0.*
11
+ requests
12
+ rich == 13.4.*
13
+ diffusers
14
+ transformers
15
+ accelerate
16
+