webscout 7.8__py3-none-any.whl → 7.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (41) hide show
  1. webscout/Bard.py +5 -25
  2. webscout/DWEBS.py +476 -476
  3. webscout/Extra/__init__.py +2 -0
  4. webscout/Extra/autocoder/__init__.py +1 -1
  5. webscout/Extra/autocoder/{rawdog.py → autocoder.py} +849 -849
  6. webscout/Extra/tempmail/__init__.py +26 -0
  7. webscout/Extra/tempmail/async_utils.py +141 -0
  8. webscout/Extra/tempmail/base.py +156 -0
  9. webscout/Extra/tempmail/cli.py +187 -0
  10. webscout/Extra/tempmail/mail_tm.py +361 -0
  11. webscout/Extra/tempmail/temp_mail_io.py +292 -0
  12. webscout/Provider/Deepinfra.py +288 -286
  13. webscout/Provider/ElectronHub.py +709 -716
  14. webscout/Provider/ExaChat.py +20 -5
  15. webscout/Provider/Gemini.py +167 -165
  16. webscout/Provider/Groq.py +38 -24
  17. webscout/Provider/LambdaChat.py +2 -1
  18. webscout/Provider/TextPollinationsAI.py +232 -230
  19. webscout/Provider/__init__.py +0 -4
  20. webscout/Provider/copilot.py +427 -427
  21. webscout/Provider/freeaichat.py +8 -1
  22. webscout/Provider/uncovr.py +312 -299
  23. webscout/Provider/yep.py +64 -12
  24. webscout/__init__.py +38 -36
  25. webscout/cli.py +293 -293
  26. webscout/conversation.py +350 -17
  27. webscout/litprinter/__init__.py +59 -667
  28. webscout/optimizers.py +419 -419
  29. webscout/update_checker.py +14 -12
  30. webscout/version.py +1 -1
  31. webscout/webscout_search.py +1282 -1282
  32. webscout/webscout_search_async.py +813 -813
  33. {webscout-7.8.dist-info → webscout-7.9.dist-info}/METADATA +44 -39
  34. {webscout-7.8.dist-info → webscout-7.9.dist-info}/RECORD +38 -35
  35. webscout/Provider/DARKAI.py +0 -225
  36. webscout/Provider/EDITEE.py +0 -192
  37. webscout/litprinter/colors.py +0 -54
  38. {webscout-7.8.dist-info → webscout-7.9.dist-info}/LICENSE.md +0 -0
  39. {webscout-7.8.dist-info → webscout-7.9.dist-info}/WHEEL +0 -0
  40. {webscout-7.8.dist-info → webscout-7.9.dist-info}/entry_points.txt +0 -0
  41. {webscout-7.8.dist-info → webscout-7.9.dist-info}/top_level.txt +0 -0
@@ -1,428 +1,428 @@
1
- import os
2
- import json
3
- import base64
4
- import asyncio
5
- import requests
6
- from urllib.parse import quote
7
- from typing import Optional, Dict, Any, List, Union, Generator
8
-
9
- from curl_cffi.requests import Session, CurlWsFlag
10
-
11
- from webscout.AIutel import Optimizers
12
- from webscout.AIutel import Conversation
13
- from webscout.AIutel import AwesomePrompts, sanitize_stream
14
- from webscout.AIbase import Provider, AsyncProvider
15
- from webscout import exceptions
16
- from webscout.litagent import LitAgent
17
-
18
- try:
19
- has_curl_cffi = True
20
- except ImportError:
21
- has_curl_cffi = False
22
-
23
- try:
24
- import nodriver
25
- has_nodriver = True
26
- except ImportError:
27
- has_nodriver = False
28
-
29
-
30
- class NoValidHarFileError(Exception):
31
- pass
32
-
33
-
34
- class CopilotConversation:
35
- conversation_id: str
36
-
37
- def __init__(self, conversation_id: str):
38
- self.conversation_id = conversation_id
39
-
40
-
41
- class Copilot(Provider):
42
- """
43
- A class to interact with the Microsoft Copilot API.
44
- """
45
-
46
- label = "Microsoft Copilot"
47
- url = "https://copilot.microsoft.com"
48
- websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
49
- conversation_url = f"{url}/c/api/conversations"
50
- AVAILABLE_MODELS = ["Copilot", "Think Deeper"]
51
- _access_token: str = None
52
- _cookies: dict = None
53
-
54
- def __init__(
55
- self,
56
- is_conversation: bool = True,
57
- max_tokens: int = 2000,
58
- timeout: int = 900,
59
- intro: str = None,
60
- filepath: str = None,
61
- update_file: bool = True,
62
- proxies: dict = {},
63
- history_offset: int = 10250,
64
- act: str = None,
65
- model: str = "Copilot"
66
- ):
67
- """Initializes the Copilot API client."""
68
- if model not in self.AVAILABLE_MODELS:
69
- raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
70
-
71
- # Use LitAgent for user-agent
72
- self.headers = {
73
- 'User-Agent': LitAgent().random(),
74
- 'Accept-Language': 'en-US,en;q=0.9',
75
- 'Connection': 'keep-alive',
76
- 'Content-Type': 'application/json',
77
- 'Origin': self.url,
78
- 'Referer': f'{self.url}/',
79
- 'Sec-Fetch-Dest': 'empty',
80
- 'Sec-Fetch-Mode': 'cors',
81
- 'Sec-Fetch-Site': 'same-origin',
82
- }
83
-
84
- self.is_conversation = is_conversation
85
- self.max_tokens_to_sample = max_tokens
86
- self.timeout = timeout
87
- self.last_response = {}
88
- self.model = model
89
- self.proxies = proxies
90
-
91
- self.__available_optimizers = (
92
- method
93
- for method in dir(Optimizers)
94
- if callable(getattr(Optimizers, method)) and not method.startswith("__")
95
- )
96
- Conversation.intro = (
97
- AwesomePrompts().get_act(
98
- act, raise_not_found=True, default=None, case_insensitive=True
99
- )
100
- if act
101
- else intro or Conversation.intro
102
- )
103
-
104
- self.conversation = Conversation(
105
- is_conversation, self.max_tokens_to_sample, filepath, update_file
106
- )
107
- self.conversation.history_offset = history_offset
108
-
109
- def ask(
110
- self,
111
- prompt: str,
112
- stream: bool = True,
113
- raw: bool = False,
114
- optimizer: str = None,
115
- conversationally: bool = False,
116
- images = None,
117
- api_key: str = None,
118
- **kwargs
119
- ) -> Union[Dict[str, Any], Generator]:
120
- conversation_prompt = self.conversation.gen_complete_prompt(prompt)
121
- if optimizer:
122
- if optimizer in self.__available_optimizers:
123
- conversation_prompt = getattr(Optimizers, optimizer)(
124
- conversation_prompt if conversationally else prompt
125
- )
126
- else:
127
- raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
128
-
129
- # Main logic for calling Copilot API
130
- def for_stream():
131
- try:
132
- if not has_curl_cffi:
133
- raise Exception('Install or update "curl_cffi" package | pip install -U curl_cffi')
134
-
135
- websocket_url = self.websocket_url
136
- headers = None
137
-
138
- if images is not None:
139
- if api_key is not None:
140
- self._access_token = api_key
141
- if self._access_token is None:
142
- try:
143
- self._access_token, self._cookies = readHAR(self.url)
144
- except NoValidHarFileError as h:
145
- # print(f"Copilot: {h}")
146
- if has_nodriver:
147
- yield {"type": "login", "provider": self.label, "url": os.environ.get("webscout_login", "")}
148
- self._access_token, self._cookies = asyncio.run(get_access_token_and_cookies(self.url, self.proxies.get("https")))
149
- else:
150
- raise h
151
- websocket_url = f"{websocket_url}&accessToken={quote(self._access_token)}"
152
- headers = {"authorization": f"Bearer {self._access_token}"}
153
-
154
- with Session(
155
- timeout=self.timeout,
156
- proxy=self.proxies.get("https"),
157
- impersonate="chrome",
158
- headers=headers,
159
- cookies=self._cookies,
160
- ) as session:
161
- if self._access_token is not None:
162
- self._cookies = session.cookies.jar if hasattr(session.cookies, "jar") else session.cookies
163
-
164
- response = session.get(f"{self.url}/c/api/user")
165
- if response.status_code == 401:
166
- raise exceptions.AuthenticationError("Status 401: Invalid access token")
167
- if response.status_code != 200:
168
- raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
169
- user = response.json().get('firstName')
170
- if user is None:
171
- self._access_token = None
172
- # print(f"Copilot: User: {user or 'null'}")
173
-
174
- # Create or use existing conversation
175
- conversation = kwargs.get("conversation", None)
176
- if conversation is None:
177
- response = session.post(self.conversation_url)
178
- if response.status_code != 200:
179
- raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
180
- conversation_id = response.json().get("id")
181
- conversation = CopilotConversation(conversation_id)
182
- if kwargs.get("return_conversation", False):
183
- yield conversation
184
- # print(f"Copilot: Created conversation: {conversation_id}")
185
- else:
186
- conversation_id = conversation.conversation_id
187
- # print(f"Copilot: Use conversation: {conversation_id}")
188
-
189
- # Handle image uploads if any
190
- uploaded_images = []
191
- if images is not None:
192
- for image, _ in images:
193
- # Convert image to bytes if needed
194
- if isinstance(image, str):
195
- if image.startswith("data:"):
196
- # Data URL
197
- header, encoded = image.split(",", 1)
198
- data = base64.b64decode(encoded)
199
- else:
200
- # File path or URL
201
- with open(image, "rb") as f:
202
- data = f.read()
203
- else:
204
- data = image
205
-
206
- # Get content type
207
- content_type = "image/jpeg" # Default
208
- if data[:2] == b'\xff\xd8':
209
- content_type = "image/jpeg"
210
- elif data[:8] == b'\x89PNG\r\n\x1a\n':
211
- content_type = "image/png"
212
- elif data[:6] in (b'GIF87a', b'GIF89a'):
213
- content_type = "image/gif"
214
- elif data[:2] in (b'BM', b'BA'):
215
- content_type = "image/bmp"
216
-
217
- response = session.post(
218
- f"{self.url}/c/api/attachments",
219
- headers={"content-type": content_type},
220
- data=data
221
- )
222
- if response.status_code != 200:
223
- raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
224
- uploaded_images.append({"type":"image", "url": response.json().get("url")})
225
- break
226
-
227
- # Connect to WebSocket
228
- wss = session.ws_connect(websocket_url)
229
- wss.send(json.dumps({"event":"setOptions","supportedCards":["weather","local","image","sports","video","ads","finance"],"ads":{"supportedTypes":["multimedia","product","tourActivity","propertyPromotion","text"]}}));
230
- wss.send(json.dumps({
231
- "event": "send",
232
- "conversationId": conversation_id,
233
- "content": [*uploaded_images, {
234
- "type": "text",
235
- "text": conversation_prompt,
236
- }],
237
- "mode": "reasoning" if "Think" in self.model else "chat"
238
- }).encode(), CurlWsFlag.TEXT)
239
-
240
- # Process response
241
- is_started = False
242
- msg = None
243
- image_prompt: str = None
244
- last_msg = None
245
- streaming_text = ""
246
-
247
- try:
248
- while True:
249
- try:
250
- msg = wss.recv()[0]
251
- msg = json.loads(msg)
252
- except:
253
- break
254
- last_msg = msg
255
- if msg.get("event") == "appendText":
256
- is_started = True
257
- content = msg.get("text")
258
- streaming_text += content
259
- resp = {"text": content}
260
- yield resp if raw else resp
261
- elif msg.get("event") == "generatingImage":
262
- image_prompt = msg.get("prompt")
263
- elif msg.get("event") == "imageGenerated":
264
- yield {"type": "image", "url": msg.get("url"), "prompt": image_prompt, "preview": msg.get("thumbnailUrl")}
265
- elif msg.get("event") == "done":
266
- break
267
- elif msg.get("event") == "suggestedFollowups":
268
- yield {"type": "suggested_followups", "suggestions": msg.get("suggestions")}
269
- break
270
- elif msg.get("event") == "replaceText":
271
- content = msg.get("text")
272
- streaming_text += content
273
- resp = {"text": content}
274
- yield resp if raw else resp
275
- elif msg.get("event") == "error":
276
- raise exceptions.FailedToGenerateResponseError(f"Error: {msg}")
277
- elif msg.get("event") not in ["received", "startMessage", "citation", "partCompleted"]:
278
- print(f"Copilot Message: {msg}")
279
-
280
- if not is_started:
281
- raise exceptions.FailedToGenerateResponseError(f"Invalid response: {last_msg}")
282
-
283
- # Update conversation history
284
- self.conversation.update_chat_history(prompt, streaming_text)
285
- self.last_response = {"text": streaming_text}
286
-
287
- finally:
288
- wss.close()
289
-
290
- except requests.RequestException as e:
291
- raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
292
- except Exception as e:
293
- raise exceptions.FailedToGenerateResponseError(f"Error: {str(e)}")
294
-
295
- def for_non_stream():
296
- streaming_text = ""
297
- for response in for_stream():
298
- if isinstance(response, dict) and "text" in response:
299
- streaming_text += response["text"]
300
- self.last_response = {"text": streaming_text}
301
- return self.last_response
302
-
303
- return for_stream() if stream else for_non_stream()
304
-
305
- def chat(
306
- self,
307
- prompt: str,
308
- stream: bool = True,
309
- optimizer: str = None,
310
- conversationally: bool = False,
311
- images = None,
312
- api_key: str = None,
313
- **kwargs
314
- ) -> Union[str, Generator]:
315
- def for_stream():
316
- for response in self.ask(prompt, True, optimizer=optimizer,
317
- conversationally=conversationally,
318
- images=images, api_key=api_key, **kwargs):
319
- if isinstance(response, dict):
320
- if "text" in response:
321
- yield response["text"]
322
- elif "type" in response:
323
- if response["type"] == "image":
324
- yield f"\n![Image]({response['url']})\n"
325
- elif response["type"] == "suggested_followups":
326
- yield "\nSuggested follow-up questions:\n"
327
- for suggestion in response["suggestions"]:
328
- yield f"- {suggestion}\n"
329
-
330
- def for_non_stream():
331
- response = self.ask(prompt, False, optimizer=optimizer,
332
- conversationally=conversationally,
333
- images=images, api_key=api_key, **kwargs)
334
- return self.get_message(response)
335
-
336
- return for_stream() if stream else for_non_stream()
337
-
338
- def get_message(self, response: dict) -> str:
339
- assert isinstance(response, dict), "Response should be of dict data-type only"
340
- return response.get("text", "")
341
-
342
-
343
- async def get_access_token_and_cookies(url: str, proxy: str = None, target: str = "ChatAI"):
344
- browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="copilot")
345
- try:
346
- page = await browser.get(url)
347
- access_token = None
348
- while access_token is None:
349
- access_token = await page.evaluate("""
350
- (() => {
351
- for (var i = 0; i < localStorage.length; i++) {
352
- try {
353
- item = JSON.parse(localStorage.getItem(localStorage.key(i)));
354
- if (item.credentialType == "AccessToken"
355
- && item.expiresOn > Math.floor(Date.now() / 1000)
356
- && item.target.includes("target")) {
357
- return item.secret;
358
- }
359
- } catch(e) {}
360
- }
361
- })()
362
- """.replace('"target"', json.dumps(target)))
363
- if access_token is None:
364
- await asyncio.sleep(1)
365
- cookies = {}
366
- for c in await page.send(nodriver.cdp.network.get_cookies([url])):
367
- cookies[c.name] = c.value
368
- await page.close()
369
- return access_token, cookies
370
- finally:
371
- stop_browser()
372
-
373
-
374
- def readHAR(url: str):
375
- api_key = None
376
- cookies = None
377
- har_files = []
378
- # Look for HAR files in common locations
379
- har_paths = [
380
- os.path.join(os.path.expanduser("~"), "Downloads"),
381
- os.path.join(os.path.expanduser("~"), "Desktop")
382
- ]
383
- for path in har_paths:
384
- if os.path.exists(path):
385
- for file in os.listdir(path):
386
- if file.endswith(".har"):
387
- har_files.append(os.path.join(path, file))
388
-
389
- for path in har_files:
390
- with open(path, 'rb') as file:
391
- try:
392
- harFile = json.loads(file.read())
393
- except json.JSONDecodeError:
394
- # Error: not a HAR file!
395
- continue
396
- for v in harFile['log']['entries']:
397
- if v['request']['url'].startswith(url):
398
- v_headers = {h['name'].lower(): h['value'] for h in v['request']['headers']}
399
- if "authorization" in v_headers:
400
- api_key = v_headers["authorization"].split(maxsplit=1).pop()
401
- if v['request']['cookies']:
402
- cookies = {c['name']: c['value'] for c in v['request']['cookies']}
403
- if api_key is None:
404
- raise NoValidHarFileError("No access token found in .har files")
405
-
406
- return api_key, cookies
407
-
408
-
409
- # def get_clarity() -> bytes:
410
- # body = base64.b64decode("H4sIAAAAAAAAA23RwU7DMAwG4HfJ2aqS2E5ibjxH1cMOnQYqYZvUTQPx7vyJRGGAemj01XWcP+9udg+j80MetDhSyrEISc5GrqrtZnmaTydHbrdUnSsWYT2u+8Obo0Ce/IQvaDBmjkwhUlKKIRNHmQgosqEArWPRDQMx90rxeUMPzB1j+UJvwNIxhTvsPcXyX1T+rizE4juK3mEEhpAUg/JvzW1/+U/tB1LATmhqotoiweMea50PLy2vui4LOY3XfD1dwnkor5fn/e18XBFgm6fHjSzZmCyV7d3aRByAEYextaTHEH3i5pgKGVP/s+DScE5PuLKIpW6FnCi1gY3Rbpqmj0/DI/+L7QEAAA==")
411
- # return body
412
-
413
-
414
- async def get_nodriver(proxy=None, user_data_dir=None):
415
- browser = await nodriver.Browser(
416
- headless=True,
417
- proxy=proxy,
418
- user_data_dir=user_data_dir
419
- )
420
- return browser, lambda: browser.close()
421
-
422
-
423
- if __name__ == "__main__":
424
- from rich import print
425
- ai = Copilot(timeout=900, model="Think Deeper")
426
- response = ai.chat(input("> "), stream=True)
427
- for chunk in response:
1
+ import os
2
+ import json
3
+ import base64
4
+ import asyncio
5
+ import requests
6
+ from urllib.parse import quote
7
+ from typing import Optional, Dict, Any, List, Union, Generator
8
+
9
+ from curl_cffi.requests import Session, CurlWsFlag
10
+
11
+ from webscout.AIutel import Optimizers
12
+ from webscout.AIutel import Conversation
13
+ from webscout.AIutel import AwesomePrompts, sanitize_stream
14
+ from webscout.AIbase import Provider, AsyncProvider
15
+ from webscout import exceptions
16
+ from webscout.litagent import LitAgent
17
+
18
+ try:
19
+ has_curl_cffi = True
20
+ except ImportError:
21
+ has_curl_cffi = False
22
+
23
+ try:
24
+ import nodriver
25
+ has_nodriver = True
26
+ except ImportError:
27
+ has_nodriver = False
28
+
29
+
30
+ class NoValidHarFileError(Exception):
31
+ pass
32
+
33
+
34
+ class CopilotConversation:
35
+ conversation_id: str
36
+
37
+ def __init__(self, conversation_id: str):
38
+ self.conversation_id = conversation_id
39
+
40
+
41
+ class Copilot(Provider):
42
+ """
43
+ A class to interact with the Microsoft Copilot API.
44
+ """
45
+
46
+ label = "Microsoft Copilot"
47
+ url = "https://copilot.microsoft.com"
48
+ websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
49
+ conversation_url = f"{url}/c/api/conversations"
50
+ AVAILABLE_MODELS = ["Copilot", "Think Deeper"]
51
+ _access_token: str = None
52
+ _cookies: dict = None
53
+
54
+ def __init__(
55
+ self,
56
+ is_conversation: bool = True,
57
+ max_tokens: int = 2000,
58
+ timeout: int = 900,
59
+ intro: str = None,
60
+ filepath: str = None,
61
+ update_file: bool = True,
62
+ proxies: dict = {},
63
+ history_offset: int = 10250,
64
+ act: str = None,
65
+ model: str = "Copilot"
66
+ ):
67
+ """Initializes the Copilot API client."""
68
+ if model not in self.AVAILABLE_MODELS:
69
+ raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
70
+
71
+ # Use LitAgent for user-agent
72
+ self.headers = {
73
+ 'User-Agent': LitAgent().random(),
74
+ 'Accept-Language': 'en-US,en;q=0.9',
75
+ 'Connection': 'keep-alive',
76
+ 'Content-Type': 'application/json',
77
+ 'Origin': self.url,
78
+ 'Referer': f'{self.url}/',
79
+ 'Sec-Fetch-Dest': 'empty',
80
+ 'Sec-Fetch-Mode': 'cors',
81
+ 'Sec-Fetch-Site': 'same-origin',
82
+ }
83
+
84
+ self.is_conversation = is_conversation
85
+ self.max_tokens_to_sample = max_tokens
86
+ self.timeout = timeout
87
+ self.last_response = {}
88
+ self.model = model
89
+ self.proxies = proxies
90
+
91
+ self.__available_optimizers = (
92
+ method
93
+ for method in dir(Optimizers)
94
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
95
+ )
96
+ Conversation.intro = (
97
+ AwesomePrompts().get_act(
98
+ act, raise_not_found=True, default=None, case_insensitive=True
99
+ )
100
+ if act
101
+ else intro or Conversation.intro
102
+ )
103
+
104
+ self.conversation = Conversation(
105
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
106
+ )
107
+ self.conversation.history_offset = history_offset
108
+
109
+ def ask(
110
+ self,
111
+ prompt: str,
112
+ stream: bool = True,
113
+ raw: bool = False,
114
+ optimizer: str = None,
115
+ conversationally: bool = False,
116
+ images = None,
117
+ api_key: str = None,
118
+ **kwargs
119
+ ) -> Union[Dict[str, Any], Generator]:
120
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
121
+ if optimizer:
122
+ if optimizer in self.__available_optimizers:
123
+ conversation_prompt = getattr(Optimizers, optimizer)(
124
+ conversation_prompt if conversationally else prompt
125
+ )
126
+ else:
127
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
128
+
129
+ # Main logic for calling Copilot API
130
+ def for_stream():
131
+ try:
132
+ if not has_curl_cffi:
133
+ raise Exception('Install or update "curl_cffi" package | pip install -U curl_cffi')
134
+
135
+ websocket_url = self.websocket_url
136
+ headers = None
137
+
138
+ if images is not None:
139
+ if api_key is not None:
140
+ self._access_token = api_key
141
+ if self._access_token is None:
142
+ try:
143
+ self._access_token, self._cookies = readHAR(self.url)
144
+ except NoValidHarFileError as h:
145
+ # print(f"Copilot: {h}")
146
+ if has_nodriver:
147
+ yield {"type": "login", "provider": self.label, "url": os.environ.get("webscout_login", "")}
148
+ self._access_token, self._cookies = asyncio.run(get_access_token_and_cookies(self.url, self.proxies.get("https")))
149
+ else:
150
+ raise h
151
+ websocket_url = f"{websocket_url}&accessToken={quote(self._access_token)}"
152
+ headers = {"authorization": f"Bearer {self._access_token}"}
153
+
154
+ with Session(
155
+ timeout=self.timeout,
156
+ proxy=self.proxies.get("https"),
157
+ impersonate="chrome",
158
+ headers=headers,
159
+ cookies=self._cookies,
160
+ ) as session:
161
+ if self._access_token is not None:
162
+ self._cookies = session.cookies.jar if hasattr(session.cookies, "jar") else session.cookies
163
+
164
+ response = session.get(f"{self.url}/c/api/user")
165
+ if response.status_code == 401:
166
+ raise exceptions.AuthenticationError("Status 401: Invalid access token")
167
+ if response.status_code != 200:
168
+ raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
169
+ user = response.json().get('firstName')
170
+ if user is None:
171
+ self._access_token = None
172
+ # print(f"Copilot: User: {user or 'null'}")
173
+
174
+ # Create or use existing conversation
175
+ conversation = kwargs.get("conversation", None)
176
+ if conversation is None:
177
+ response = session.post(self.conversation_url)
178
+ if response.status_code != 200:
179
+ raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
180
+ conversation_id = response.json().get("id")
181
+ conversation = CopilotConversation(conversation_id)
182
+ if kwargs.get("return_conversation", False):
183
+ yield conversation
184
+ # print(f"Copilot: Created conversation: {conversation_id}")
185
+ else:
186
+ conversation_id = conversation.conversation_id
187
+ # print(f"Copilot: Use conversation: {conversation_id}")
188
+
189
+ # Handle image uploads if any
190
+ uploaded_images = []
191
+ if images is not None:
192
+ for image, _ in images:
193
+ # Convert image to bytes if needed
194
+ if isinstance(image, str):
195
+ if image.startswith("data:"):
196
+ # Data URL
197
+ header, encoded = image.split(",", 1)
198
+ data = base64.b64decode(encoded)
199
+ else:
200
+ # File path or URL
201
+ with open(image, "rb") as f:
202
+ data = f.read()
203
+ else:
204
+ data = image
205
+
206
+ # Get content type
207
+ content_type = "image/jpeg" # Default
208
+ if data[:2] == b'\xff\xd8':
209
+ content_type = "image/jpeg"
210
+ elif data[:8] == b'\x89PNG\r\n\x1a\n':
211
+ content_type = "image/png"
212
+ elif data[:6] in (b'GIF87a', b'GIF89a'):
213
+ content_type = "image/gif"
214
+ elif data[:2] in (b'BM', b'BA'):
215
+ content_type = "image/bmp"
216
+
217
+ response = session.post(
218
+ f"{self.url}/c/api/attachments",
219
+ headers={"content-type": content_type},
220
+ data=data
221
+ )
222
+ if response.status_code != 200:
223
+ raise exceptions.APIConnectionError(f"Status {response.status_code}: {response.text}")
224
+ uploaded_images.append({"type":"image", "url": response.json().get("url")})
225
+ break
226
+
227
+ # Connect to WebSocket
228
+ wss = session.ws_connect(websocket_url)
229
+ wss.send(json.dumps({"event":"setOptions","supportedCards":["weather","local","image","sports","video","ads","finance"],"ads":{"supportedTypes":["multimedia","product","tourActivity","propertyPromotion","text"]}}));
230
+ wss.send(json.dumps({
231
+ "event": "send",
232
+ "conversationId": conversation_id,
233
+ "content": [*uploaded_images, {
234
+ "type": "text",
235
+ "text": conversation_prompt,
236
+ }],
237
+ "mode": "reasoning" if "Think" in self.model else "chat"
238
+ }).encode(), CurlWsFlag.TEXT)
239
+
240
+ # Process response
241
+ is_started = False
242
+ msg = None
243
+ image_prompt: str = None
244
+ last_msg = None
245
+ streaming_text = ""
246
+
247
+ try:
248
+ while True:
249
+ try:
250
+ msg = wss.recv()[0]
251
+ msg = json.loads(msg)
252
+ except:
253
+ break
254
+ last_msg = msg
255
+ if msg.get("event") == "appendText":
256
+ is_started = True
257
+ content = msg.get("text")
258
+ streaming_text += content
259
+ resp = {"text": content}
260
+ yield resp if raw else resp
261
+ elif msg.get("event") == "generatingImage":
262
+ image_prompt = msg.get("prompt")
263
+ elif msg.get("event") == "imageGenerated":
264
+ yield {"type": "image", "url": msg.get("url"), "prompt": image_prompt, "preview": msg.get("thumbnailUrl")}
265
+ elif msg.get("event") == "done":
266
+ break
267
+ elif msg.get("event") == "suggestedFollowups":
268
+ yield {"type": "suggested_followups", "suggestions": msg.get("suggestions")}
269
+ break
270
+ elif msg.get("event") == "replaceText":
271
+ content = msg.get("text")
272
+ streaming_text += content
273
+ resp = {"text": content}
274
+ yield resp if raw else resp
275
+ elif msg.get("event") == "error":
276
+ raise exceptions.FailedToGenerateResponseError(f"Error: {msg}")
277
+ elif msg.get("event") not in ["received", "startMessage", "citation", "partCompleted"]:
278
+ print(f"Copilot Message: {msg}")
279
+
280
+ if not is_started:
281
+ raise exceptions.FailedToGenerateResponseError(f"Invalid response: {last_msg}")
282
+
283
+ # Update conversation history
284
+ self.conversation.update_chat_history(prompt, streaming_text)
285
+ self.last_response = {"text": streaming_text}
286
+
287
+ finally:
288
+ wss.close()
289
+
290
+ except requests.RequestException as e:
291
+ raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
292
+ except Exception as e:
293
+ raise exceptions.FailedToGenerateResponseError(f"Error: {str(e)}")
294
+
295
+ def for_non_stream():
296
+ streaming_text = ""
297
+ for response in for_stream():
298
+ if isinstance(response, dict) and "text" in response:
299
+ streaming_text += response["text"]
300
+ self.last_response = {"text": streaming_text}
301
+ return self.last_response
302
+
303
+ return for_stream() if stream else for_non_stream()
304
+
305
+ def chat(
306
+ self,
307
+ prompt: str,
308
+ stream: bool = True,
309
+ optimizer: str = None,
310
+ conversationally: bool = False,
311
+ images = None,
312
+ api_key: str = None,
313
+ **kwargs
314
+ ) -> Union[str, Generator]:
315
+ def for_stream():
316
+ for response in self.ask(prompt, True, optimizer=optimizer,
317
+ conversationally=conversationally,
318
+ images=images, api_key=api_key, **kwargs):
319
+ if isinstance(response, dict):
320
+ if "text" in response:
321
+ yield response["text"]
322
+ elif "type" in response:
323
+ if response["type"] == "image":
324
+ yield f"\n![Image]({response['url']})\n"
325
+ elif response["type"] == "suggested_followups":
326
+ yield "\nSuggested follow-up questions:\n"
327
+ for suggestion in response["suggestions"]:
328
+ yield f"- {suggestion}\n"
329
+
330
+ def for_non_stream():
331
+ response = self.ask(prompt, False, optimizer=optimizer,
332
+ conversationally=conversationally,
333
+ images=images, api_key=api_key, **kwargs)
334
+ return self.get_message(response)
335
+
336
+ return for_stream() if stream else for_non_stream()
337
+
338
+ def get_message(self, response: dict) -> str:
339
+ assert isinstance(response, dict), "Response should be of dict data-type only"
340
+ return response.get("text", "")
341
+
342
+
343
+ async def get_access_token_and_cookies(url: str, proxy: str = None, target: str = "ChatAI"):
344
+ browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="copilot")
345
+ try:
346
+ page = await browser.get(url)
347
+ access_token = None
348
+ while access_token is None:
349
+ access_token = await page.evaluate("""
350
+ (() => {
351
+ for (var i = 0; i < localStorage.length; i++) {
352
+ try {
353
+ item = JSON.parse(localStorage.getItem(localStorage.key(i)));
354
+ if (item.credentialType == "AccessToken"
355
+ && item.expiresOn > Math.floor(Date.now() / 1000)
356
+ && item.target.includes("target")) {
357
+ return item.secret;
358
+ }
359
+ } catch(e) {}
360
+ }
361
+ })()
362
+ """.replace('"target"', json.dumps(target)))
363
+ if access_token is None:
364
+ await asyncio.sleep(1)
365
+ cookies = {}
366
+ for c in await page.send(nodriver.cdp.network.get_cookies([url])):
367
+ cookies[c.name] = c.value
368
+ await page.close()
369
+ return access_token, cookies
370
+ finally:
371
+ stop_browser()
372
+
373
+
374
+ def readHAR(url: str):
375
+ api_key = None
376
+ cookies = None
377
+ har_files = []
378
+ # Look for HAR files in common locations
379
+ har_paths = [
380
+ os.path.join(os.path.expanduser("~"), "Downloads"),
381
+ os.path.join(os.path.expanduser("~"), "Desktop")
382
+ ]
383
+ for path in har_paths:
384
+ if os.path.exists(path):
385
+ for file in os.listdir(path):
386
+ if file.endswith(".har"):
387
+ har_files.append(os.path.join(path, file))
388
+
389
+ for path in har_files:
390
+ with open(path, 'rb') as file:
391
+ try:
392
+ harFile = json.loads(file.read())
393
+ except json.JSONDecodeError:
394
+ # Error: not a HAR file!
395
+ continue
396
+ for v in harFile['log']['entries']:
397
+ if v['request']['url'].startswith(url):
398
+ v_headers = {h['name'].lower(): h['value'] for h in v['request']['headers']}
399
+ if "authorization" in v_headers:
400
+ api_key = v_headers["authorization"].split(maxsplit=1).pop()
401
+ if v['request']['cookies']:
402
+ cookies = {c['name']: c['value'] for c in v['request']['cookies']}
403
+ if api_key is None:
404
+ raise NoValidHarFileError("No access token found in .har files")
405
+
406
+ return api_key, cookies
407
+
408
+
409
+ # def get_clarity() -> bytes:
410
+ # body = base64.b64decode("H4sIAAAAAAAAA23RwU7DMAwG4HfJ2aqS2E5ibjxH1cMOnQYqYZvUTQPx7vyJRGGAemj01XWcP+9udg+j80MetDhSyrEISc5GrqrtZnmaTydHbrdUnSsWYT2u+8Obo0Ce/IQvaDBmjkwhUlKKIRNHmQgosqEArWPRDQMx90rxeUMPzB1j+UJvwNIxhTvsPcXyX1T+rizE4juK3mEEhpAUg/JvzW1/+U/tB1LATmhqotoiweMea50PLy2vui4LOY3XfD1dwnkor5fn/e18XBFgm6fHjSzZmCyV7d3aRByAEYextaTHEH3i5pgKGVP/s+DScE5PuLKIpW6FnCi1gY3Rbpqmj0/DI/+L7QEAAA==")
411
+ # return body
412
+
413
+
414
+ async def get_nodriver(proxy=None, user_data_dir=None):
415
+ browser = await nodriver.Browser(
416
+ headless=True,
417
+ proxy=proxy,
418
+ user_data_dir=user_data_dir
419
+ )
420
+ return browser, lambda: browser.close()
421
+
422
+
423
+ if __name__ == "__main__":
424
+ from rich import print
425
+ ai = Copilot(timeout=900, model="Think Deeper")
426
+ response = ai.chat(input("> "), stream=True)
427
+ for chunk in response:
428
428
  print(chunk, end="", flush=True)