webscout 6.9__py3-none-any.whl → 7.0__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.

webscout/AIbase.py CHANGED
@@ -1,5 +1,4 @@
1
- from abc import ABC
2
- from abc import abstractmethod
1
+ from abc import ABC, abstractmethod
3
2
  from pathlib import Path
4
3
  from typing import AsyncGenerator, List, Union, Generator, Optional
5
4
  from typing_extensions import TypeAlias
@@ -110,4 +109,15 @@ class AsyncImageProvider(ABC):
110
109
  name: Optional[str] = None,
111
110
  dir: Optional[Union[str, Path]] = None
112
111
  ) -> List[str]:
112
+ raise NotImplementedError("Method needs to be implemented in subclass")
113
+
114
+ class AISearch(ABC):
115
+
116
+ @abstractmethod
117
+ def search(
118
+ self,
119
+ prompt: str,
120
+ stream: bool = False,
121
+ raw: bool = False,
122
+ ) -> Response:
113
123
  raise NotImplementedError("Method needs to be implemented in subclass")
webscout/DWEBS.py CHANGED
@@ -109,7 +109,7 @@ class GoogleS:
109
109
  timeout: Optional[int] = 10,
110
110
  max_workers: int = 20,
111
111
  cache_dir: Optional[str] = None,
112
- rate_limit: float = 0.01,
112
+ rate_limit: float = 2.0,
113
113
  use_litlogger: bool = False
114
114
  ):
115
115
  """
@@ -152,7 +152,10 @@ class GoogleS:
152
152
  current_time = time.time()
153
153
  time_since_last = current_time - self.last_request_time
154
154
  if time_since_last < self.rate_limit:
155
- time.sleep(self.rate_limit - time_since_last)
155
+ sleep_time = self.rate_limit - time_since_last
156
+ if self.use_litlogger:
157
+ self.logger.debug(f"Rate limiting: Waiting {sleep_time:.2f} seconds")
158
+ time.sleep(sleep_time)
156
159
  self.last_request_time = time.time()
157
160
 
158
161
  def _get_url(self, method: str, url: str, params: Optional[Dict[str, str]] = None,
@@ -170,31 +173,44 @@ class GoogleS:
170
173
  Returns:
171
174
  bytes: Response content
172
175
  """
173
- self._respect_rate_limit()
176
+ retry_count = 0
177
+ base_delay = 5 # Base delay in seconds
174
178
 
175
- for attempt in range(max_retries):
179
+ while retry_count < max_retries:
176
180
  try:
177
- if self.use_litlogger:
178
- self.logger.debug(f"Making {method} request to {url} (Attempt {attempt + 1})")
181
+ self._respect_rate_limit()
182
+ response = self.client.request(
183
+ method=method,
184
+ url=url,
185
+ params=params,
186
+ data=data,
187
+ timeout=self.timeout
188
+ )
179
189
 
180
- resp = self.client.request(method, url, params=params, data=data, timeout=self.timeout)
181
- resp.raise_for_status()
190
+ if response.status_code == 429:
191
+ retry_delay = base_delay * (2 ** retry_count) # Exponential backoff
192
+ if self.use_litlogger:
193
+ self.logger.warning(f"Rate limited by Google. Waiting {retry_delay} seconds before retry...")
194
+ time.sleep(retry_delay)
195
+ retry_count += 1
196
+ continue
197
+
198
+ response.raise_for_status()
199
+ return response.content
182
200
 
183
- if self.use_litlogger:
184
- self.logger.success(f"Request successful: {resp.status_code}")
201
+ except requests.exceptions.RequestException as e:
202
+ if retry_count == max_retries - 1:
203
+ if self.use_litlogger:
204
+ self.logger.error(f"Max retries reached. Last error: {str(e)}")
205
+ raise
185
206
 
186
- return resp.content
187
-
188
- except requests.exceptions.RequestException as ex:
207
+ retry_delay = base_delay * (2 ** retry_count)
189
208
  if self.use_litlogger:
190
- self.logger.error(f"Request failed: {url} - {str(ex)}")
209
+ self.logger.warning(f"Request failed. Retrying in {retry_delay} seconds... Error: {str(e)}")
210
+ time.sleep(retry_delay)
211
+ retry_count += 1
191
212
 
192
- # Exponential backoff
193
- if attempt < max_retries - 1:
194
- wait_time = (2 ** attempt) + random.random()
195
- time.sleep(wait_time)
196
- else:
197
- raise
213
+ raise Exception("Max retries reached")
198
214
 
199
215
  @lru_cache(maxsize=100)
200
216
  def _cache_key(self, query: str, **kwargs) -> str:
@@ -471,7 +487,7 @@ class GoogleS:
471
487
 
472
488
  if __name__ == "__main__":
473
489
  from rich import print
474
- searcher = GoogleS()
475
- results = searcher.search("HelpingAI-9B", max_results=200, extract_text=False, max_text_length=200)
490
+ searcher = GoogleS(rate_limit=3.0, use_litlogger=True)
491
+ results = searcher.search("HelpingAI-9B", max_results=5, extract_text=False, max_text_length=200)
476
492
  for result in results:
477
493
  print(result)
@@ -8,14 +8,71 @@ import sys
8
8
  from typing import List, Optional
9
9
 
10
10
  from webscout.optimizers import Optimizers
11
-
11
+ import os
12
+ import platform
13
+ import subprocess
12
14
 
13
15
  def get_current_app() -> str:
14
- """Get the current active application name."""
15
- try:
16
- active_window: Optional[gw.Window] = gw.getActiveWindow()
17
- return f"{active_window.title if active_window else 'Unknown'}"
18
- except Exception as e:
16
+ """
17
+ Get the current active application or window title in a cross-platform manner.
18
+
19
+ On Windows, uses the win32gui module from pywin32.
20
+ On macOS, uses AppKit to access the active application info.
21
+ On Linux, uses xprop to get the active window details.
22
+
23
+ Returns:
24
+ A string containing the title of the active application/window, or "Unknown" if it cannot be determined.
25
+ """
26
+ system_name = platform.system()
27
+
28
+ if system_name == "Windows":
29
+ try:
30
+ import win32gui # pywin32 must be installed
31
+ window_handle = win32gui.GetForegroundWindow()
32
+ title = win32gui.GetWindowText(window_handle)
33
+ return title if title else "Unknown"
34
+ except Exception:
35
+ return "Unknown"
36
+
37
+ elif system_name == "Darwin": # macOS
38
+ try:
39
+ from AppKit import NSWorkspace # type: ignore
40
+ active_app = NSWorkspace.sharedWorkspace().activeApplication()
41
+ title = active_app.get('NSApplicationName')
42
+ return title if title else "Unknown"
43
+ except Exception:
44
+ return "Unknown"
45
+
46
+ elif system_name == "Linux":
47
+ try:
48
+ # Get the active window id using xprop
49
+ result = subprocess.run(
50
+ ["xprop", "-root", "_NET_ACTIVE_WINDOW"],
51
+ stdout=subprocess.PIPE,
52
+ stderr=subprocess.PIPE,
53
+ text=True
54
+ )
55
+ if result.returncode == 0 and result.stdout:
56
+ # Expected format: _NET_ACTIVE_WINDOW(WINDOW): window id # 0x1400007
57
+ parts = result.stdout.strip().split()
58
+ window_id = parts[-1]
59
+ if window_id != "0x0":
60
+ title_result = subprocess.run(
61
+ ["xprop", "-id", window_id, "WM_NAME"],
62
+ stdout=subprocess.PIPE,
63
+ stderr=subprocess.PIPE,
64
+ text=True
65
+ )
66
+ if title_result.returncode == 0 and title_result.stdout:
67
+ # Expected format: WM_NAME(STRING) = "Terminal"
68
+ title_parts = title_result.stdout.split(" = ", 1)
69
+ if len(title_parts) == 2:
70
+ title = title_parts[1].strip().strip('"')
71
+ return title if title else "Unknown"
72
+ except Exception:
73
+ pass
74
+ return "Unknown"
75
+ else:
19
76
  return "Unknown"
20
77
 
21
78
 
@@ -136,4 +193,8 @@ EXAMPLES: str = """
136
193
  </rawdog_response>
137
194
  </example>
138
195
  </examples>
139
- """
196
+ """
197
+
198
+ if __name__ == "__main__":
199
+ # Simple test harness to print the current active window title.
200
+ print("Current Active Window/Application: ", get_current_app())
@@ -1,21 +1,5 @@
1
1
  """
2
- Yo fam! 🔥 Welcome to AutoLlama - your go-to tool for downloading and setting up HelpingAI models! 💪
3
-
4
- Created by the legendary Abhay Koul, this script's got your back when it comes to:
5
- - Downloading models straight from HuggingFace Hub 🚀
6
- - Setting up Ollama with zero hassle 💯
7
- - Getting your AI assistant ready to vibe with you! ⚡
8
-
9
- Usage:
10
2
  >>> python -m webscout.Extra.autollama download -m "OEvortex/HelpingAI-Lite-1.5T" -g "HelpingAI-Lite-1.5T.q4_k_m.gguf"
11
-
12
- Features:
13
- - Smart model management 🧠
14
- - Automatic dependency installation 📦
15
- - Progress tracking that keeps it real 📈
16
- - Error handling that's got your back 💪
17
-
18
- Join the squad on Discord and level up your AI game! 🎮
19
3
  """
20
4
 
21
5
  import warnings
webscout/Extra/gguf.py CHANGED
@@ -1,22 +1,9 @@
1
1
  """
2
- Yo fam! 🔥 Welcome to GGUF Converter - your ultimate tool for converting models to GGUF format! 💪
3
2
 
4
- - Converting HuggingFace models to GGUF format 🚀
5
- - Multiple quantization methods for different needs 🎯
6
- - Easy upload back to HuggingFace Hub 📤
7
-
8
- Usage:
9
3
  >>> python -m webscout.Extra.gguf convert -m "OEvortex/HelpingAI-Lite-1.5T" -q "q4_k_m,q5_k_m"
10
4
  >>> # With upload options:
11
5
  >>> python -m webscout.Extra.gguf convert -m "your-model" -u "username" -t "token" -q "q4_k_m"
12
6
 
13
- Features:
14
- - Smart dependency checking 🔍
15
- - CUDA support detection ⚡
16
- - Progress tracking that keeps it real 📈
17
- - Multiple quantization options 🎮
18
-
19
- Join the squad on Discord and level up your AI game! 🎮
20
7
  """
21
8
 
22
9
  import subprocess
@@ -0,0 +1,251 @@
1
+ from uuid import uuid4
2
+ import requests
3
+ import json
4
+ import re
5
+ from typing import Any, Dict, Generator, Optional
6
+
7
+ from webscout.AIbase import AISearch
8
+ from webscout import exceptions
9
+ from webscout import LitAgent
10
+
11
+ class Response:
12
+ """A wrapper class for DeepFind API responses.
13
+
14
+ This class automatically converts response objects to their text representation
15
+ when printed or converted to string.
16
+
17
+ Attributes:
18
+ text (str): The text content of the response
19
+
20
+ Example:
21
+ >>> response = Response("Hello, world!")
22
+ >>> print(response)
23
+ Hello, world!
24
+ >>> str(response)
25
+ 'Hello, world!'
26
+ """
27
+ def __init__(self, text: str):
28
+ self.text = text
29
+
30
+ def __str__(self):
31
+ return self.text
32
+
33
+ def __repr__(self):
34
+ return self.text
35
+
36
+ class DeepFind(AISearch):
37
+ """A class to interact with the DeepFind AI search API.
38
+
39
+ DeepFind provides a powerful search interface that returns AI-generated responses
40
+ based on web content. It supports both streaming and non-streaming responses.
41
+
42
+ Basic Usage:
43
+ >>> from webscout import DeepFind
44
+ >>> ai = DeepFind()
45
+ >>> # Non-streaming example
46
+ >>> response = ai.search("What is Python?")
47
+ >>> print(response)
48
+ Python is a high-level programming language...
49
+
50
+ >>> # Streaming example
51
+ >>> for chunk in ai.search("Tell me about AI", stream=True):
52
+ ... print(chunk, end="", flush=True)
53
+ Artificial Intelligence is...
54
+
55
+ >>> # Raw response format
56
+ >>> for chunk in ai.search("Hello", stream=True, raw=True):
57
+ ... print(chunk)
58
+ {'text': 'Hello'}
59
+ {'text': ' there!'}
60
+
61
+ Args:
62
+ timeout (int, optional): Request timeout in seconds. Defaults to 30.
63
+ proxies (dict, optional): Proxy configuration for requests. Defaults to None.
64
+
65
+ Attributes:
66
+ api_endpoint (str): The DeepFind API endpoint URL.
67
+ stream_chunk_size (int): Size of chunks when streaming responses.
68
+ timeout (int): Request timeout in seconds.
69
+ headers (dict): HTTP headers used in requests.
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ timeout: int = 30,
75
+ proxies: Optional[dict] = None,
76
+ ):
77
+ """Initialize the DeepFind API client.
78
+
79
+ Args:
80
+ timeout (int, optional): Request timeout in seconds. Defaults to 30.
81
+ proxies (dict, optional): Proxy configuration for requests. Defaults to None.
82
+
83
+ Example:
84
+ >>> ai = DeepFind(timeout=60) # Longer timeout
85
+ >>> ai = DeepFind(proxies={'http': 'http://proxy.com:8080'}) # With proxy
86
+ """
87
+ self.session = requests.Session()
88
+ self.api_endpoint = "https://www.deepfind.co/?q={query}"
89
+ self.stream_chunk_size = 1024
90
+ self.timeout = timeout
91
+ self.last_response = {}
92
+ self.headers = {
93
+ "Accept": "text/x-component",
94
+ "Accept-Encoding": "gzip, deflate, br, zstd",
95
+ "Accept-Language": "en-US,en;q=0.9,en-IN;q=0.8",
96
+ "Content-Type": "text/plain;charset=UTF-8",
97
+ "DNT": "1",
98
+ "Next-Action": "f354668f23f516a46ad0abe4dedb84b19068bb54",
99
+ "Next-Router-State-Tree": '%5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%3F%7B%5C%22q%5C%22%3A%5C%22hi%5C%22%7D%22%2C%7B%7D%2C%22%2F%3Fq%3Dhi%22%2C%22refresh%22%5D%7D%2Cnull%2Cnull%2Ctrue%5D',
100
+ "Origin": "https://www.deepfind.co",
101
+ "Referer": "https://www.deepfind.co/?q=hi",
102
+ "Sec-Ch-Ua": '"Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"',
103
+ "Sec-Ch-Ua-Mobile": "?0",
104
+ "Sec-Ch-Ua-Platform": '"Windows"',
105
+ "Sec-Fetch-Dest": "empty",
106
+ "Sec-Fetch-Mode": "cors",
107
+ "Sec-Fetch-Site": "same-origin",
108
+ "User-Agent": LitAgent().random(),
109
+ }
110
+ self.session.headers.update(self.headers)
111
+ self.proxies = proxies
112
+
113
+ def search(
114
+ self,
115
+ prompt: str,
116
+ stream: bool = False,
117
+ raw: bool = False,
118
+ ) -> Dict[str, Any] | Generator[str, None, None]:
119
+ """Search using the DeepFind API and get AI-generated responses.
120
+
121
+ This method sends a search query to DeepFind and returns the AI-generated response.
122
+ It supports both streaming and non-streaming modes, as well as raw response format.
123
+
124
+ Args:
125
+ prompt (str): The search query or prompt to send to the API.
126
+ stream (bool, optional): If True, yields response chunks as they arrive.
127
+ If False, returns complete response. Defaults to False.
128
+ raw (bool, optional): If True, returns raw response dictionaries with 'text' key.
129
+ If False, returns Response objects that convert to text automatically.
130
+ Defaults to False.
131
+
132
+ Returns:
133
+ Union[Dict[str, Any], Generator[str, None, None]]:
134
+ - If stream=False: Returns complete response
135
+ - If stream=True: Yields response chunks as they arrive
136
+
137
+ Raises:
138
+ APIConnectionError: If the API request fails
139
+
140
+ Examples:
141
+ Basic search:
142
+ >>> ai = DeepFind()
143
+ >>> response = ai.search("What is Python?")
144
+ >>> print(response)
145
+ Python is a programming language...
146
+
147
+ Streaming response:
148
+ >>> for chunk in ai.search("Tell me about AI", stream=True):
149
+ ... print(chunk, end="")
150
+ Artificial Intelligence...
151
+
152
+ Raw response format:
153
+ >>> for chunk in ai.search("Hello", stream=True, raw=True):
154
+ ... print(chunk)
155
+ {'text': 'Hello'}
156
+ {'text': ' there!'}
157
+
158
+ Error handling:
159
+ >>> try:
160
+ ... response = ai.search("My question")
161
+ ... except exceptions.APIConnectionError as e:
162
+ ... print(f"API error: {e}")
163
+ """
164
+ url = self.api_endpoint.format(query=prompt)
165
+ payload = [
166
+ [{"role": "user", "id": uuid4().hex, "content": prompt}],
167
+ uuid4().hex,
168
+ ]
169
+
170
+ def for_stream():
171
+ try:
172
+ with self.session.post(
173
+ url,
174
+ headers=self.headers,
175
+ json=payload,
176
+ stream=True,
177
+ timeout=self.timeout,
178
+ ) as response:
179
+ response.raise_for_status()
180
+ streaming_text = ""
181
+ for line in response.iter_lines(decode_unicode=True):
182
+ if line:
183
+ content_matches = re.findall(r'"content":"([^"\\]*(?:\\.[^"\\]*)*)"', line)
184
+ if content_matches:
185
+ for content in content_matches:
186
+ if len(content) > len(streaming_text):
187
+ delta = content[len(streaming_text):]
188
+ streaming_text = content
189
+ delta = delta.replace('\\"', '"').replace('\\n', '\n')
190
+ delta = re.sub(r'\[REF\]\(https?://[^\s]*\)', '', delta)
191
+ if raw:
192
+ yield {"text": delta}
193
+ else:
194
+ yield Response(delta)
195
+ description_matches = re.findall(r'"description":"([^"\\]*(?:\\.[^"\\]*)*)"', line)
196
+ if description_matches:
197
+ for description in description_matches:
198
+ if description and len(description) > len(streaming_text):
199
+ delta = description[len(streaming_text):]
200
+ streaming_text = description
201
+ delta = delta.replace('\\"', '"').replace('\\n', '\n')
202
+ delta = re.sub(r'\[REF\]\(https?://[^\s]*\)', '', delta)
203
+ if raw:
204
+ yield {"text": f"{delta}\n"}
205
+ else:
206
+ yield Response(f"{delta}\n")
207
+ self.last_response = Response(streaming_text)
208
+ except requests.exceptions.RequestException as e:
209
+ raise exceptions.APIConnectionError(f"Request failed: {e}")
210
+
211
+ def for_non_stream():
212
+ full_response = ""
213
+ for chunk in for_stream():
214
+ if raw:
215
+ yield chunk
216
+ else:
217
+ full_response += str(chunk)
218
+ if not raw:
219
+ self.last_response = Response(full_response)
220
+ return self.last_response
221
+
222
+ return for_stream() if stream else for_non_stream()
223
+
224
+ @staticmethod
225
+ def clean_content(text: str) -> str:
226
+ """Removes all webblock elements with research or detail classes.
227
+
228
+ Args:
229
+ text (str): The text to clean
230
+
231
+ Returns:
232
+ str: The cleaned text
233
+
234
+ Example:
235
+ >>> text = '<webblock class="research">...</webblock>Other text'
236
+ >>> cleaned_text = DeepFind.clean_content(text)
237
+ >>> print(cleaned_text)
238
+ Other text
239
+ """
240
+ cleaned_text = re.sub(
241
+ r'<webblock class="(?:research|detail)">[^<]*</webblock>', "", text
242
+ )
243
+ return cleaned_text
244
+
245
+
246
+ if __name__ == "__main__":
247
+ from rich import print
248
+ ai = DeepFind()
249
+ response = ai.search(input(">>> "), stream=True, raw=False)
250
+ for chunk in response:
251
+ print(chunk, end="", flush=True)
@@ -1,2 +1,2 @@
1
- from .felo_search import *
2
- from .ooai import *
1
+ from .felo_search import Felo
2
+ from .DeepFind import DeepFind