webscout 8.3.1__py3-none-any.whl → 8.3.2__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 (77) hide show
  1. webscout/AIutel.py +46 -53
  2. webscout/Bing_search.py +418 -0
  3. webscout/Extra/gguf.py +706 -177
  4. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  5. webscout/Provider/GeminiProxy.py +140 -0
  6. webscout/Provider/MCPCore.py +78 -75
  7. webscout/Provider/OPENAI/BLACKBOXAI.py +1 -4
  8. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  9. webscout/Provider/OPENAI/README.md +2 -0
  10. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  11. webscout/Provider/OPENAI/__init__.py +15 -1
  12. webscout/Provider/OPENAI/autoproxy.py +332 -39
  13. webscout/Provider/OPENAI/base.py +15 -5
  14. webscout/Provider/OPENAI/e2b.py +0 -1
  15. webscout/Provider/OPENAI/mcpcore.py +109 -70
  16. webscout/Provider/OPENAI/scirachat.py +59 -51
  17. webscout/Provider/OPENAI/toolbaz.py +2 -9
  18. webscout/Provider/OPENAI/xenai.py +514 -0
  19. webscout/Provider/OPENAI/yep.py +8 -2
  20. webscout/Provider/TTI/__init__.py +1 -0
  21. webscout/Provider/TTI/bing.py +231 -0
  22. webscout/Provider/TTS/speechma.py +45 -39
  23. webscout/Provider/TogetherAI.py +366 -0
  24. webscout/Provider/XenAI.py +324 -0
  25. webscout/Provider/__init__.py +8 -3
  26. webscout/Provider/deepseek_assistant.py +378 -0
  27. webscout/auth/__init__.py +44 -0
  28. webscout/auth/api_key_manager.py +189 -0
  29. webscout/auth/auth_system.py +100 -0
  30. webscout/auth/config.py +76 -0
  31. webscout/auth/database.py +400 -0
  32. webscout/auth/exceptions.py +67 -0
  33. webscout/auth/middleware.py +248 -0
  34. webscout/auth/models.py +130 -0
  35. webscout/auth/providers.py +257 -0
  36. webscout/auth/rate_limiter.py +254 -0
  37. webscout/auth/request_models.py +127 -0
  38. webscout/auth/request_processing.py +226 -0
  39. webscout/auth/routes.py +526 -0
  40. webscout/auth/schemas.py +103 -0
  41. webscout/auth/server.py +312 -0
  42. webscout/auth/static/favicon.svg +11 -0
  43. webscout/auth/swagger_ui.py +203 -0
  44. webscout/auth/templates/components/authentication.html +237 -0
  45. webscout/auth/templates/components/base.html +103 -0
  46. webscout/auth/templates/components/endpoints.html +750 -0
  47. webscout/auth/templates/components/examples.html +491 -0
  48. webscout/auth/templates/components/footer.html +75 -0
  49. webscout/auth/templates/components/header.html +27 -0
  50. webscout/auth/templates/components/models.html +286 -0
  51. webscout/auth/templates/components/navigation.html +70 -0
  52. webscout/auth/templates/static/api.js +455 -0
  53. webscout/auth/templates/static/icons.js +168 -0
  54. webscout/auth/templates/static/main.js +784 -0
  55. webscout/auth/templates/static/particles.js +201 -0
  56. webscout/auth/templates/static/styles.css +3353 -0
  57. webscout/auth/templates/static/ui.js +374 -0
  58. webscout/auth/templates/swagger_ui.html +170 -0
  59. webscout/client.py +49 -3
  60. webscout/scout/core/scout.py +104 -26
  61. webscout/scout/element.py +139 -18
  62. webscout/swiftcli/core/cli.py +14 -3
  63. webscout/swiftcli/decorators/output.py +59 -9
  64. webscout/update_checker.py +31 -49
  65. webscout/version.py +1 -1
  66. webscout/webscout_search.py +4 -12
  67. webscout/webscout_search_async.py +3 -10
  68. webscout/yep_search.py +2 -11
  69. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
  70. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/RECORD +74 -36
  71. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
  72. webscout/Provider/HF_space/__init__.py +0 -0
  73. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  74. webscout/Provider/OPENAI/api.py +0 -1320
  75. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
  76. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
  77. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,231 @@
1
+ import requests
2
+ import time
3
+ import tempfile
4
+ import os
5
+ from typing import Optional
6
+ from webscout.Provider.TTI.utils import ImageData, ImageResponse
7
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
8
+ from io import BytesIO
9
+ from webscout.litagent import LitAgent
10
+
11
+ try:
12
+ from PIL import Image
13
+ except ImportError:
14
+ Image = None
15
+
16
+ class Images(BaseImages):
17
+ def __init__(self, client):
18
+ self._client = client
19
+
20
+ def create(
21
+ self,
22
+ *,
23
+ model: str = "bing",
24
+ prompt: str,
25
+ n: int = 1,
26
+ size: str = "1024x1024",
27
+ response_format: str = "url",
28
+ user: Optional[str] = None,
29
+ style: str = "none",
30
+ aspect_ratio: str = "1:1",
31
+ timeout: int = 60,
32
+ image_format: str = "png",
33
+ seed: Optional[int] = None,
34
+ **kwargs
35
+ ) -> ImageResponse:
36
+ if not prompt:
37
+ raise ValueError("Parameter 'prompt' is required")
38
+ if Image is None:
39
+ raise ImportError("Pillow (PIL) is required for image format conversion.")
40
+ agent = LitAgent()
41
+ session = self._client.session
42
+ headers = self._client.headers
43
+ images = []
44
+ urls = []
45
+ for _ in range(n):
46
+ data = {
47
+ "q": prompt,
48
+ "rt": "4",
49
+ "FORM": "GENCRE"
50
+ }
51
+ response = session.post(
52
+ "https://www.bing.com/images/create",
53
+ data=data,
54
+ headers=headers,
55
+ allow_redirects=False,
56
+ timeout=timeout
57
+ )
58
+ redirect_url = response.headers.get("Location")
59
+ if not redirect_url:
60
+ raise Exception("Failed to get redirect URL")
61
+ from urllib.parse import urlparse, parse_qs
62
+ query = urlparse(redirect_url).query
63
+ request_id = parse_qs(query).get("id", [None])[0]
64
+ if not request_id:
65
+ raise Exception("ID not found in URL")
66
+ polling_url = f"https://www.bing.com/images/create/async/results/{request_id}?q={requests.utils.quote(prompt)}"
67
+ attempts = 0
68
+ img_url = None
69
+ while attempts < 10:
70
+ time.sleep(3)
71
+ try:
72
+ poll_resp = session.get(polling_url, headers=headers, timeout=timeout)
73
+ from bs4 import BeautifulSoup
74
+ soup = BeautifulSoup(poll_resp.text, "html.parser")
75
+ imgs = [img["src"].split("?")[0] for img in soup.select(".img_cont .mimg") if img.get("src")]
76
+ if imgs:
77
+ img_url = imgs[0]
78
+ break
79
+ except Exception:
80
+ pass
81
+ attempts += 1
82
+ if not img_url:
83
+ raise Exception("Failed to get images after polling.")
84
+ img_bytes = session.get(img_url, headers=headers, timeout=timeout).content
85
+ # Convert to png or jpeg in memory
86
+ with BytesIO(img_bytes) as input_io:
87
+ with Image.open(input_io) as im:
88
+ out_io = BytesIO()
89
+ if image_format.lower() == "jpeg":
90
+ im = im.convert("RGB")
91
+ im.save(out_io, format="JPEG")
92
+ else:
93
+ im.save(out_io, format="PNG")
94
+ img_bytes = out_io.getvalue()
95
+ images.append(img_bytes)
96
+ if response_format == "url":
97
+ def upload_file_with_retry(img_bytes, image_format, max_retries=3):
98
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
99
+ for attempt in range(max_retries):
100
+ tmp_path = None
101
+ try:
102
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
103
+ tmp.write(img_bytes)
104
+ tmp.flush()
105
+ tmp_path = tmp.name
106
+ with open(tmp_path, "rb") as f:
107
+ files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
108
+ data = {"reqtype": "fileupload", "json": "true"}
109
+ headers2 = {"User-Agent": agent.random()}
110
+ if attempt > 0:
111
+ headers2["Connection"] = "close"
112
+ resp2 = requests.post(
113
+ "https://catbox.moe/user/api.php",
114
+ files=files,
115
+ data=data,
116
+ headers=headers2,
117
+ timeout=timeout,
118
+ )
119
+ if resp2.status_code == 200 and resp2.text.strip():
120
+ text = resp2.text.strip()
121
+ if text.startswith("http"):
122
+ return text
123
+ try:
124
+ result = resp2.json()
125
+ if "url" in result:
126
+ return result["url"]
127
+ except Exception:
128
+ if "http" in text:
129
+ return text
130
+ except Exception:
131
+ if attempt < max_retries - 1:
132
+ time.sleep(1 * (attempt + 1))
133
+ finally:
134
+ if tmp_path and os.path.isfile(tmp_path):
135
+ try:
136
+ os.remove(tmp_path)
137
+ except Exception:
138
+ pass
139
+ return None
140
+ def upload_file_alternative(img_bytes, image_format):
141
+ try:
142
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
143
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
144
+ tmp.write(img_bytes)
145
+ tmp.flush()
146
+ tmp_path = tmp.name
147
+ try:
148
+ if not os.path.isfile(tmp_path):
149
+ return None
150
+ with open(tmp_path, "rb") as img_file:
151
+ files = {"file": img_file}
152
+ alt_resp = requests.post("https://0x0.st", files=files)
153
+ alt_resp.raise_for_status()
154
+ image_url = alt_resp.text.strip()
155
+ if not image_url.startswith("http"):
156
+ return None
157
+ return image_url
158
+ except Exception:
159
+ return None
160
+ finally:
161
+ try:
162
+ os.remove(tmp_path)
163
+ except Exception:
164
+ pass
165
+ except Exception:
166
+ return None
167
+ uploaded_url = upload_file_with_retry(img_bytes, image_format)
168
+ if not uploaded_url:
169
+ uploaded_url = upload_file_alternative(img_bytes, image_format)
170
+ if uploaded_url:
171
+ urls.append(uploaded_url)
172
+ else:
173
+ raise RuntimeError(
174
+ "Failed to upload image to catbox.moe using all available methods"
175
+ )
176
+ result_data = []
177
+ if response_format == "url":
178
+ for url in urls:
179
+ result_data.append(ImageData(url=url))
180
+ elif response_format == "b64_json":
181
+ import base64
182
+ for img in images:
183
+ b64 = base64.b64encode(img).decode("utf-8")
184
+ result_data.append(ImageData(b64_json=b64))
185
+ else:
186
+ raise ValueError("response_format must be 'url' or 'b64_json'")
187
+ from time import time as _time
188
+ return ImageResponse(created=int(_time()), data=result_data)
189
+
190
+ class BingImageAI(TTICompatibleProvider):
191
+ AVAILABLE_MODELS = ["bing"]
192
+ def __init__(self, cookie: Optional[str] = None):
193
+ self.session = requests.Session()
194
+ self.user_agent = LitAgent().random()
195
+ self.headers = {
196
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
197
+ "accept-language": "id-ID,id;q=0.9",
198
+ "cache-control": "max-age=0",
199
+ "content-type": "application/x-www-form-urlencoded",
200
+ "origin": "https://www.bing.com",
201
+ "referer": "https://www.bing.com/images/create?&wlexpsignin=1",
202
+ "sec-ch-ua": '"Chromium";v="131", "Not_A Brand";v="24", "Microsoft Edge Simulate";v="131", "Lemur";v="131"',
203
+ "sec-ch-ua-mobile": "?1",
204
+ "sec-fetch-site": "same-origin",
205
+ "sec-fetch-mode": "navigate",
206
+ "sec-fetch-dest": "document",
207
+ "upgrade-insecure-requests": "1",
208
+ "user-agent": self.user_agent,
209
+ }
210
+ self.session.headers.update(self.headers)
211
+ self.cookie = cookie
212
+ if cookie:
213
+ self.session.cookies.set("_U", cookie, domain="bing.com")
214
+ self.images = Images(self)
215
+ @property
216
+ def models(self):
217
+ class _ModelList:
218
+ def list(inner_self):
219
+ return type(self).AVAILABLE_MODELS
220
+ return _ModelList()
221
+
222
+ if __name__ == "__main__":
223
+ from rich import print
224
+ client = BingImageAI(cookie="1pkdvumH1SEjFkDjFymRYKouIRoXZlh_p5RTfAttx4DaaNOSDyz8qFP2M7LbZ93fbl4f6Xm8fTGwXHNDB648Gom5jfnTU_Iz-VH47l0HTYJDS1sItbBBS-sqSISFgXR62SoqnW5eX5MFht-j2uB1gZ4uDnpR_60fLRTCdW1SIRegDvnBm1TGhRiZsi6wUPyzwFg7-PsXAs3Fq9iV9m-0FEw")
225
+ response = client.images.create(
226
+ prompt="A cat riding a bicycle",
227
+ response_format="url",
228
+ n=4,
229
+ timeout=30
230
+ )
231
+ print(response)
@@ -1,15 +1,12 @@
1
1
  ##################################################################################
2
2
  ## Modified version of code written by t.me/infip1217 ##
3
3
  ##################################################################################
4
- import time
5
4
  import requests
6
5
  import pathlib
7
6
  import tempfile
8
- from io import BytesIO
9
7
  from webscout import exceptions
10
8
  from webscout.litagent import LitAgent
11
- from concurrent.futures import ThreadPoolExecutor, as_completed
12
- from webscout.Provider.TTS import utils
9
+ from webscout.Litlogger import Logger, LogLevel
13
10
  from webscout.Provider.TTS.base import BaseTTSProvider
14
11
 
15
12
  class SpeechMaTTS(BaseTTSProvider):
@@ -18,12 +15,11 @@ class SpeechMaTTS(BaseTTSProvider):
18
15
  """
19
16
  # Request headers
20
17
  headers = {
21
- "accept": "*/*",
22
- "accept-language": "en-IN,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,en-AU;q=0.6",
23
- "content-type": "application/json",
18
+ "authority": "speechma.com",
24
19
  "origin": "https://speechma.com",
25
- "priority": "u=1, i",
26
- "User-Agent": LitAgent().random()
20
+ "referer": "https://speechma.com/",
21
+ "content-type": "application/json",
22
+ **LitAgent().generate_fingerprint()
27
23
  }
28
24
 
29
25
  # Available voices with their IDs
@@ -496,8 +492,9 @@ class SpeechMaTTS(BaseTTSProvider):
496
492
  if proxies:
497
493
  self.session.proxies.update(proxies)
498
494
  self.timeout = timeout
495
+ self.logger = Logger(name="SpeechMaTTS", level=LogLevel.INFO)
499
496
 
500
- def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0) -> str:
497
+ def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0, verbose: bool = False) -> str:
501
498
  """
502
499
  Converts text to speech using the SpeechMa API and saves it to a file.
503
500
 
@@ -506,6 +503,7 @@ class SpeechMaTTS(BaseTTSProvider):
506
503
  voice (str): The voice to use for TTS (default: "Emma")
507
504
  pitch (int): Voice pitch adjustment (-10 to 10, default: 0)
508
505
  rate (int): Voice rate/speed adjustment (-10 to 10, default: 0)
506
+ verbose (bool): Whether to print debug information (default: False)
509
507
 
510
508
  Returns:
511
509
  str: Path to the generated audio file
@@ -517,10 +515,13 @@ class SpeechMaTTS(BaseTTSProvider):
517
515
  voice in self.all_voices
518
516
  ), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
519
517
 
518
+ if not text or text.strip() == '':
519
+ raise exceptions.FailedToGenerateResponseError("Text is empty")
520
+
520
521
  filename = pathlib.Path(tempfile.mktemp(suffix=".mp3", dir=self.temp_dir))
521
522
  voice_id = self.all_voices[voice]
522
523
 
523
- # Prepare payload for the job-based API
524
+ # Prepare payload for the API
524
525
  payload = {
525
526
  "text": text,
526
527
  "voice": voice_id,
@@ -530,44 +531,49 @@ class SpeechMaTTS(BaseTTSProvider):
530
531
  }
531
532
 
532
533
  try:
534
+ # Set logger level based on verbose flag
535
+ if verbose:
536
+ self.logger.level = LogLevel.DEBUG
537
+ self.logger.debug(f"Generating audio for voice: {voice} ({voice_id})")
538
+ self.logger.debug(f"Text length: {len(text)} characters")
539
+ else:
540
+ self.logger.level = LogLevel.INFO
541
+
542
+ # Make the request to the SpeechMa API
533
543
  response = self.session.post(
534
544
  self.api_url,
535
545
  headers=self.headers,
536
546
  json=payload,
537
547
  timeout=self.timeout
538
548
  )
539
- response.raise_for_status()
540
- resp_json = response.json()
541
- if not resp_json.get("success") or "data" not in resp_json or "job_id" not in resp_json["data"]:
542
- raise exceptions.FailedToGenerateResponseError(f"SpeechMa API error: {resp_json}")
543
- job_id = resp_json["data"]["job_id"]
544
549
 
545
- # Poll for job completion
546
- status_url = f"https://speechma.com/com.api/tts-api.php/status/{job_id}"
547
- for _ in range(30): # up to ~30 seconds
548
- status_resp = self.session.get(
549
- status_url,
550
- headers=self.headers,
551
- timeout=self.timeout
552
- )
553
- status_resp.raise_for_status()
554
- status_json = status_resp.json()
555
- if status_json.get("success") and status_json.get("data", {}).get("status") == "completed":
556
- break
557
- time.sleep(1)
558
- else:
559
- raise exceptions.FailedToGenerateResponseError("TTS job did not complete in time.")
550
+ if response.status_code != 200:
551
+ if verbose:
552
+ self.logger.error(f"API error: Status {response.status_code}")
553
+ raise exceptions.FailedToGenerateResponseError(f"API returned status {response.status_code}: {response.text[:500]}")
560
554
 
561
- # Download the audio file (API provides a URL in the status response)
562
- data = status_json["data"]
563
- audio_url = f"https://speechma.com/com.api/tts-api.php/audio/{job_id}"
564
- audio_resp = self.session.get(audio_url, timeout=self.timeout)
565
- audio_resp.raise_for_status()
566
- with open(filename, 'wb') as f:
567
- f.write(audio_resp.content)
568
- return filename.as_posix()
555
+ # Check if response is audio data (content-type should be audio/mpeg)
556
+ content_type = response.headers.get('content-type', '').lower()
557
+ if verbose:
558
+ self.logger.debug(f"Response content type: {content_type}")
559
+ self.logger.debug(f"Response size: {len(response.content)} bytes")
560
+
561
+ if 'audio' in content_type or response.content.startswith(b'\xff\xfb') or response.content.startswith(b'ID3') or b'LAME' in response.content[:100]:
562
+ # This is audio data, save it directly
563
+ with open(filename, 'wb') as f:
564
+ f.write(response.content)
565
+ if verbose:
566
+ self.logger.debug(f"Audio saved to: {filename}")
567
+ return filename.as_posix()
568
+ else:
569
+ # Unexpected response format
570
+ if verbose:
571
+ self.logger.error(f"Unexpected response format: {content_type}")
572
+ raise exceptions.FailedToGenerateResponseError(f"Unexpected response format. Content-Type: {content_type}, Content: {response.text[:200]}")
569
573
 
570
574
  except requests.exceptions.RequestException as e:
575
+ if verbose:
576
+ self.logger.error(f"Request failed: {e}")
571
577
  raise exceptions.FailedToGenerateResponseError(
572
578
  f"Failed to perform the operation: {e}"
573
579
  )