webscout 7.9__py3-none-any.whl → 8.1__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 (69) hide show
  1. webscout/Extra/GitToolkit/__init__.py +10 -0
  2. webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
  3. webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
  4. webscout/Extra/GitToolkit/gitapi/user.py +96 -0
  5. webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
  6. webscout/Extra/YTToolkit/ytapi/video.py +232 -103
  7. webscout/Provider/AISEARCH/DeepFind.py +1 -1
  8. webscout/Provider/AISEARCH/ISou.py +1 -1
  9. webscout/Provider/AISEARCH/__init__.py +6 -1
  10. webscout/Provider/AISEARCH/felo_search.py +1 -1
  11. webscout/Provider/AISEARCH/genspark_search.py +1 -1
  12. webscout/Provider/AISEARCH/hika_search.py +194 -0
  13. webscout/Provider/AISEARCH/iask_search.py +436 -0
  14. webscout/Provider/AISEARCH/monica_search.py +246 -0
  15. webscout/Provider/AISEARCH/scira_search.py +320 -0
  16. webscout/Provider/AISEARCH/webpilotai_search.py +281 -0
  17. webscout/Provider/AllenAI.py +255 -122
  18. webscout/Provider/DeepSeek.py +1 -2
  19. webscout/Provider/Deepinfra.py +17 -9
  20. webscout/Provider/ExaAI.py +261 -0
  21. webscout/Provider/ExaChat.py +8 -1
  22. webscout/Provider/GithubChat.py +2 -1
  23. webscout/Provider/Jadve.py +2 -2
  24. webscout/Provider/Netwrck.py +3 -2
  25. webscout/Provider/OPENAI/__init__.py +17 -0
  26. webscout/Provider/OPENAI/base.py +46 -0
  27. webscout/Provider/OPENAI/c4ai.py +347 -0
  28. webscout/Provider/OPENAI/chatgptclone.py +460 -0
  29. webscout/Provider/OPENAI/deepinfra.py +284 -0
  30. webscout/Provider/OPENAI/exaai.py +419 -0
  31. webscout/Provider/OPENAI/exachat.py +421 -0
  32. webscout/Provider/OPENAI/freeaichat.py +355 -0
  33. webscout/Provider/OPENAI/glider.py +314 -0
  34. webscout/Provider/OPENAI/heckai.py +337 -0
  35. webscout/Provider/OPENAI/llmchatco.py +325 -0
  36. webscout/Provider/OPENAI/netwrck.py +348 -0
  37. webscout/Provider/OPENAI/scirachat.py +459 -0
  38. webscout/Provider/OPENAI/sonus.py +294 -0
  39. webscout/Provider/OPENAI/typegpt.py +361 -0
  40. webscout/Provider/OPENAI/utils.py +211 -0
  41. webscout/Provider/OPENAI/venice.py +428 -0
  42. webscout/Provider/OPENAI/wisecat.py +381 -0
  43. webscout/Provider/OPENAI/x0gpt.py +389 -0
  44. webscout/Provider/OPENAI/yep.py +329 -0
  45. webscout/Provider/OpenGPT.py +199 -0
  46. webscout/Provider/PI.py +39 -24
  47. webscout/Provider/Venice.py +1 -1
  48. webscout/Provider/Youchat.py +326 -296
  49. webscout/Provider/__init__.py +16 -6
  50. webscout/Provider/ai4chat.py +58 -56
  51. webscout/Provider/akashgpt.py +34 -22
  52. webscout/Provider/freeaichat.py +1 -1
  53. webscout/Provider/labyrinth.py +121 -20
  54. webscout/Provider/llmchatco.py +306 -0
  55. webscout/Provider/scira_chat.py +274 -0
  56. webscout/Provider/typefully.py +280 -0
  57. webscout/Provider/typegpt.py +3 -184
  58. webscout/prompt_manager.py +2 -1
  59. webscout/version.py +1 -1
  60. webscout/webscout_search.py +118 -54
  61. webscout/webscout_search_async.py +109 -45
  62. webscout-8.1.dist-info/METADATA +683 -0
  63. {webscout-7.9.dist-info → webscout-8.1.dist-info}/RECORD +67 -33
  64. webscout/Provider/flowith.py +0 -207
  65. webscout-7.9.dist-info/METADATA +0 -995
  66. {webscout-7.9.dist-info → webscout-8.1.dist-info}/LICENSE.md +0 -0
  67. {webscout-7.9.dist-info → webscout-8.1.dist-info}/WHEEL +0 -0
  68. {webscout-7.9.dist-info → webscout-8.1.dist-info}/entry_points.txt +0 -0
  69. {webscout-7.9.dist-info → webscout-8.1.dist-info}/top_level.txt +0 -0
@@ -91,7 +91,7 @@ class WEBS:
91
91
  if not proxy and proxies:
92
92
  warnings.warn("'proxies' is deprecated, use 'proxy' instead.", stacklevel=1)
93
93
  self.proxy = proxies.get("http") or proxies.get("https") if isinstance(proxies, dict) else proxies
94
-
94
+
95
95
  default_headers = {
96
96
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
97
97
  "Accept-Language": "en-US,en;q=0.5",
@@ -105,10 +105,10 @@ class WEBS:
105
105
  "Sec-Fetch-User": "?1",
106
106
  "Referer": "https://duckduckgo.com/",
107
107
  }
108
-
108
+
109
109
  self.headers = headers if headers else {}
110
110
  self.headers.update(default_headers)
111
-
111
+
112
112
  self.client = primp.Client(
113
113
  headers=self.headers,
114
114
  proxy=self.proxy,
@@ -192,18 +192,36 @@ class WEBS:
192
192
  resp_content = self._get_url("GET", "https://duckduckgo.com", params={"q": keywords}).content
193
193
  return _extract_vqd(resp_content, keywords)
194
194
 
195
- def chat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> Iterator[str]:
195
+ def chat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> Iterator[str]:
196
196
  """Initiates a chat session with webscout AI.
197
197
 
198
198
  Args:
199
199
  keywords (str): The initial message or question to send to the AI.
200
200
  model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
201
201
  "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
202
- timeout (int): Timeout value for the HTTP client. Defaults to 20.
202
+ timeout (int): Timeout value for the HTTP client. Defaults to 30.
203
+ max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
203
204
 
204
205
  Yields:
205
206
  str: Chunks of the response from the AI.
206
207
  """
208
+ # Get Cloudflare Turnstile token
209
+ def get_turnstile_token():
210
+ try:
211
+ # Visit the DuckDuckGo chat page to get the Turnstile token
212
+ resp_content = self._get_url(
213
+ method="GET",
214
+ url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
215
+ ).content
216
+
217
+ # Extract the Turnstile token if available
218
+ if b'cf-turnstile-response' in resp_content:
219
+ token = resp_content.split(b'cf-turnstile-response="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
220
+ return token
221
+ return ""
222
+ except Exception:
223
+ return ""
224
+
207
225
  # x-fe-version
208
226
  if not self._chat_xfe:
209
227
  resp_content = self._get_url(
@@ -231,55 +249,100 @@ class WEBS:
231
249
  if model not in self._chat_models:
232
250
  warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
233
251
  model = "gpt-4o-mini"
252
+
253
+ # Get Cloudflare Turnstile token
254
+ turnstile_token = get_turnstile_token()
255
+
234
256
  json_data = {
235
257
  "model": self._chat_models[model],
236
258
  "messages": self._chat_messages,
237
259
  }
238
- resp = self._get_url(
239
- method="POST",
240
- url="https://duckduckgo.com/duckchat/v1/chat",
241
- headers={
242
- "x-fe-version": self._chat_xfe,
243
- "x-vqd-4": self._chat_vqd,
244
- "x-vqd-hash-1": "",
245
- },
246
- json=json_data,
247
- timeout=timeout,
248
- )
249
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
250
- self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
251
- chunks = []
252
- try:
253
- for chunk in resp.stream():
254
- lines = chunk.split(b"data:")
255
- for line in lines:
256
- if line := line.strip():
257
- if line == b"[DONE]":
258
- break
259
- if line == b"[DONE][LIMIT_CONVERSATION]":
260
- raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
261
- x = json_loads(line)
262
- if isinstance(x, dict):
263
- if x.get("action") == "error":
264
- err_message = x.get("type", "")
265
- if x.get("status") == 429:
266
- raise (
267
- ConversationLimitException(err_message)
268
- if err_message == "ERR_CONVERSATION_LIMIT"
269
- else RatelimitE(err_message)
270
- )
271
- raise WebscoutE(err_message)
272
- elif message := x.get("message"):
273
- chunks.append(message)
274
- yield message
275
- except Exception as ex:
276
- raise WebscoutE(f"chat_yield() {type(ex).__name__}: {ex}") from ex
277
260
 
278
- result = "".join(chunks)
279
- self._chat_messages.append({"role": "assistant", "content": result})
280
- self._chat_tokens_count += len(result)
261
+ # Add Turnstile token if available
262
+ if turnstile_token:
263
+ json_data["cf-turnstile-response"] = turnstile_token
264
+
265
+ # Enhanced headers to better mimic a real browser
266
+ chat_headers = {
267
+ "x-fe-version": self._chat_xfe,
268
+ "x-vqd-4": self._chat_vqd,
269
+ "x-vqd-hash-1": "",
270
+ "Accept": "text/event-stream",
271
+ "Accept-Language": "en-US,en;q=0.9",
272
+ "Cache-Control": "no-cache",
273
+ "Content-Type": "application/json",
274
+ "DNT": "1",
275
+ "Origin": "https://duckduckgo.com",
276
+ "Referer": "https://duckduckgo.com/",
277
+ "Sec-Fetch-Dest": "empty",
278
+ "Sec-Fetch-Mode": "cors",
279
+ "Sec-Fetch-Site": "same-origin",
280
+ "User-Agent": self.client.headers.get("User-Agent", "")
281
+ }
281
282
 
282
- def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> str:
283
+ # Retry logic for rate limited requests
284
+ retry_count = 0
285
+ while retry_count <= max_retries:
286
+ try:
287
+ resp = self._get_url(
288
+ method="POST",
289
+ url="https://duckduckgo.com/duckchat/v1/chat",
290
+ headers=chat_headers,
291
+ json=json_data,
292
+ timeout=timeout,
293
+ )
294
+
295
+ self._chat_vqd = resp.headers.get("x-vqd-4", "")
296
+ self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
297
+ chunks = []
298
+
299
+ for chunk in resp.stream():
300
+ lines = chunk.split(b"data:")
301
+ for line in lines:
302
+ if line := line.strip():
303
+ if line == b"[DONE]":
304
+ break
305
+ if line == b"[DONE][LIMIT_CONVERSATION]":
306
+ raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
307
+ x = json_loads(line)
308
+ if isinstance(x, dict):
309
+ if x.get("action") == "error":
310
+ err_message = x.get("type", "")
311
+ if x.get("status") == 429:
312
+ raise (
313
+ ConversationLimitException(err_message)
314
+ if err_message == "ERR_CONVERSATION_LIMIT"
315
+ else RatelimitE(err_message)
316
+ )
317
+ raise WebscoutE(err_message)
318
+ elif message := x.get("message"):
319
+ chunks.append(message)
320
+ yield message
321
+
322
+ # If we get here, the request was successful
323
+ result = "".join(chunks)
324
+ self._chat_messages.append({"role": "assistant", "content": result})
325
+ self._chat_tokens_count += len(result)
326
+ return
327
+
328
+ except RatelimitE as ex:
329
+ retry_count += 1
330
+ if retry_count > max_retries:
331
+ raise WebscoutE(f"chat_yield() Rate limit exceeded after {max_retries} retries: {ex}") from ex
332
+
333
+ # Get a fresh Turnstile token for the retry
334
+ turnstile_token = get_turnstile_token()
335
+ if turnstile_token:
336
+ json_data["cf-turnstile-response"] = turnstile_token
337
+
338
+ # Exponential backoff
339
+ sleep_time = 2 ** retry_count
340
+ sleep(sleep_time)
341
+
342
+ except Exception as ex:
343
+ raise WebscoutE(f"chat_yield() {type(ex).__name__}: {ex}") from ex
344
+
345
+ def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> str:
283
346
  """Initiates a chat session with webscout AI.
284
347
 
285
348
  Args:
@@ -287,11 +350,12 @@ class WEBS:
287
350
  model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
288
351
  "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
289
352
  timeout (int): Timeout value for the HTTP client. Defaults to 30.
353
+ max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
290
354
 
291
355
  Returns:
292
356
  str: The response from the AI.
293
357
  """
294
- answer_generator = self.chat_yield(keywords, model, timeout)
358
+ answer_generator = self.chat_yield(keywords, model, timeout, max_retries)
295
359
  return "".join(answer_generator)
296
360
 
297
361
  def text(
@@ -1225,22 +1289,22 @@ class WEBS:
1225
1289
  assert location, "location is mandatory"
1226
1290
  lang = language.split('-')[0]
1227
1291
  url = f"https://duckduckgo.com/js/spice/forecast/{quote(location)}/{lang}"
1228
-
1292
+
1229
1293
  resp = self._get_url("GET", url).content
1230
1294
  resp_text = resp.decode('utf-8')
1231
-
1295
+
1232
1296
  if "ddg_spice_forecast(" not in resp_text:
1233
1297
  raise WebscoutE(f"No weather data found for {location}")
1234
-
1298
+
1235
1299
  json_text = resp_text[resp_text.find('(') + 1:resp_text.rfind(')')]
1236
1300
  try:
1237
1301
  result = json.loads(json_text)
1238
1302
  except Exception as e:
1239
1303
  raise WebscoutE(f"Error parsing weather JSON: {e}")
1240
-
1304
+
1241
1305
  if not result or 'currentWeather' not in result or 'forecastDaily' not in result:
1242
1306
  raise WebscoutE(f"Invalid weather data format for {location}")
1243
-
1307
+
1244
1308
  formatted_data = {
1245
1309
  "location": result["currentWeather"]["metadata"].get("ddg-location", "Unknown"),
1246
1310
  "current": {
@@ -174,7 +174,7 @@ class AsyncWEBS:
174
174
  resp_content = (await self._get_url("GET", "https://duckduckgo.com", params={"q": keywords})).content
175
175
  return _extract_vqd(resp_content, keywords)
176
176
 
177
- async def achat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> AsyncIterator[str]:
177
+ async def achat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> AsyncIterator[str]:
178
178
  """Initiates an async chat session with webscout AI.
179
179
 
180
180
  Args:
@@ -182,10 +182,28 @@ class AsyncWEBS:
182
182
  model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
183
183
  "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
184
184
  timeout (int): Timeout value for the HTTP client. Defaults to 30.
185
+ max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
185
186
 
186
187
  Yields:
187
188
  str: Chunks of the response from the AI.
188
189
  """
190
+ # Get Cloudflare Turnstile token
191
+ async def get_turnstile_token():
192
+ try:
193
+ # Visit the DuckDuckGo chat page to get the Turnstile token
194
+ resp_content = (await self._get_url(
195
+ method="GET",
196
+ url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
197
+ )).content
198
+
199
+ # Extract the Turnstile token if available
200
+ if b'cf-turnstile-response' in resp_content:
201
+ token = resp_content.split(b'cf-turnstile-response="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
202
+ return token
203
+ return ""
204
+ except Exception:
205
+ return ""
206
+
189
207
  # x-fe-version
190
208
  if not self._chat_xfe:
191
209
  resp_content = (await self._get_url(
@@ -213,55 +231,100 @@ class AsyncWEBS:
213
231
  if model not in self._chat_models:
214
232
  warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
215
233
  model = "gpt-4o-mini"
234
+
235
+ # Get Cloudflare Turnstile token
236
+ turnstile_token = await get_turnstile_token()
237
+
216
238
  json_data = {
217
239
  "model": self._chat_models[model],
218
240
  "messages": self._chat_messages,
219
241
  }
220
- resp = await self._get_url(
221
- method="POST",
222
- url="https://duckduckgo.com/duckchat/v1/chat",
223
- headers={
224
- "x-fe-version": self._chat_xfe,
225
- "x-vqd-4": self._chat_vqd,
226
- "x-vqd-hash-1": "",
227
- },
228
- json=json_data,
229
- timeout=timeout,
230
- )
231
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
232
- self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
233
- chunks = []
234
- try:
235
- async for chunk in resp.aiter_bytes():
236
- lines = chunk.split(b"data:")
237
- for line in lines:
238
- if line := line.strip():
239
- if line == b"[DONE]":
240
- break
241
- if line == b"[DONE][LIMIT_CONVERSATION]":
242
- raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
243
- x = json_loads(line)
244
- if isinstance(x, dict):
245
- if x.get("action") == "error":
246
- err_message = x.get("type", "")
247
- if x.get("status") == 429:
248
- raise (
249
- ConversationLimitException(err_message)
250
- if err_message == "ERR_CONVERSATION_LIMIT"
251
- else RatelimitE(err_message)
252
- )
253
- raise WebscoutE(err_message)
254
- elif message := x.get("message"):
255
- chunks.append(message)
256
- yield message
257
- except Exception as ex:
258
- raise WebscoutE(f"achat_yield() {type(ex).__name__}: {ex}") from ex
259
242
 
260
- result = "".join(chunks)
261
- self._chat_messages.append({"role": "assistant", "content": result})
262
- self._chat_tokens_count += len(result)
243
+ # Add Turnstile token if available
244
+ if turnstile_token:
245
+ json_data["cf-turnstile-response"] = turnstile_token
246
+
247
+ # Enhanced headers to better mimic a real browser
248
+ chat_headers = {
249
+ "x-fe-version": self._chat_xfe,
250
+ "x-vqd-4": self._chat_vqd,
251
+ "x-vqd-hash-1": "",
252
+ "Accept": "text/event-stream",
253
+ "Accept-Language": "en-US,en;q=0.9",
254
+ "Cache-Control": "no-cache",
255
+ "Content-Type": "application/json",
256
+ "DNT": "1",
257
+ "Origin": "https://duckduckgo.com",
258
+ "Referer": "https://duckduckgo.com/",
259
+ "Sec-Fetch-Dest": "empty",
260
+ "Sec-Fetch-Mode": "cors",
261
+ "Sec-Fetch-Site": "same-origin",
262
+ "User-Agent": self.client.headers.get("User-Agent", "")
263
+ }
264
+
265
+ # Retry logic for rate limited requests
266
+ retry_count = 0
267
+ while retry_count <= max_retries:
268
+ try:
269
+ resp = await self._get_url(
270
+ method="POST",
271
+ url="https://duckduckgo.com/duckchat/v1/chat",
272
+ headers=chat_headers,
273
+ json=json_data,
274
+ timeout=timeout,
275
+ )
276
+
277
+ self._chat_vqd = resp.headers.get("x-vqd-4", "")
278
+ self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
279
+ chunks = []
280
+
281
+ async for chunk in resp.aiter_bytes():
282
+ lines = chunk.split(b"data:")
283
+ for line in lines:
284
+ if line := line.strip():
285
+ if line == b"[DONE]":
286
+ break
287
+ if line == b"[DONE][LIMIT_CONVERSATION]":
288
+ raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
289
+ x = json_loads(line)
290
+ if isinstance(x, dict):
291
+ if x.get("action") == "error":
292
+ err_message = x.get("type", "")
293
+ if x.get("status") == 429:
294
+ raise (
295
+ ConversationLimitException(err_message)
296
+ if err_message == "ERR_CONVERSATION_LIMIT"
297
+ else RatelimitE(err_message)
298
+ )
299
+ raise WebscoutE(err_message)
300
+ elif message := x.get("message"):
301
+ chunks.append(message)
302
+ yield message
303
+
304
+ # If we get here, the request was successful
305
+ result = "".join(chunks)
306
+ self._chat_messages.append({"role": "assistant", "content": result})
307
+ self._chat_tokens_count += len(result)
308
+ return
309
+
310
+ except RatelimitE as ex:
311
+ retry_count += 1
312
+ if retry_count > max_retries:
313
+ raise WebscoutE(f"achat_yield() Rate limit exceeded after {max_retries} retries: {ex}") from ex
314
+
315
+ # Get a fresh Turnstile token for the retry
316
+ turnstile_token = await get_turnstile_token()
317
+ if turnstile_token:
318
+ json_data["cf-turnstile-response"] = turnstile_token
319
+
320
+ # Exponential backoff
321
+ sleep_time = 2 ** retry_count
322
+ await asyncio.sleep(sleep_time)
323
+
324
+ except Exception as ex:
325
+ raise WebscoutE(f"achat_yield() {type(ex).__name__}: {ex}") from ex
263
326
 
264
- async def achat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> str:
327
+ async def achat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> str:
265
328
  """Initiates an async chat session with webscout AI.
266
329
 
267
330
  Args:
@@ -269,12 +332,13 @@ class AsyncWEBS:
269
332
  model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
270
333
  "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
271
334
  timeout (int): Timeout value for the HTTP client. Defaults to 30.
335
+ max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
272
336
 
273
337
  Returns:
274
338
  str: The response from the AI.
275
339
  """
276
340
  chunks = []
277
- async for chunk in self.achat_yield(keywords, model, timeout):
341
+ async for chunk in self.achat_yield(keywords, model, timeout, max_retries):
278
342
  chunks.append(chunk)
279
343
  return "".join(chunks)
280
344