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

@@ -0,0 +1,350 @@
1
+ from typing import Any, Dict, Generator, Optional, Union
2
+
3
+ from curl_cffi import CurlError
4
+ from curl_cffi.requests import Session
5
+
6
+ from webscout import exceptions
7
+ from webscout.AIbase import Provider
8
+ from webscout.AIutel import AwesomePrompts, Conversation, Optimizers
9
+ from webscout.litagent import LitAgent
10
+
11
+
12
+ class ClaudeOnline(Provider):
13
+ """
14
+ A class to interact with the Claude Online API (claude.online/chat).
15
+
16
+ This provider implements the reverse-engineered API from claude.online,
17
+ providing access to Claude AI through their web interface backend.
18
+ """
19
+ required_auth = False
20
+ AVAILABLE_MODELS = ["claude-online"]
21
+
22
+ def __init__(
23
+ self,
24
+ is_conversation: bool = True,
25
+ max_tokens: int = 2049,
26
+ timeout: int = 30,
27
+ intro: str = None,
28
+ filepath: str = None,
29
+ update_file: bool = True,
30
+ proxies: dict = {},
31
+ history_offset: int = 10250,
32
+ act: str = None,
33
+ system_prompt: str = "You are a helpful assistant.",
34
+ model: str = "claude-online"
35
+ ):
36
+ """
37
+ Initializes the Claude Online API client.
38
+
39
+ Args:
40
+ is_conversation: Whether the provider is in conversation mode.
41
+ max_tokens: Maximum number of tokens to sample.
42
+ timeout: Timeout for API requests.
43
+ intro: Introduction message for the conversation.
44
+ filepath: Filepath for storing conversation history.
45
+ update_file: Whether to update the conversation history file.
46
+ proxies: Proxies for the API requests.
47
+ history_offset: Offset for conversation history.
48
+ act: Act for the conversation.
49
+ system_prompt: The system prompt to define the assistant's role.
50
+ model: Model to use (only "claude-online" supported).
51
+ """
52
+ if model not in self.AVAILABLE_MODELS:
53
+ raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
54
+
55
+ # API endpoints
56
+ self.chat_url = "https://wewordle.org/gptapi/v1/web/turbo"
57
+ self.limit_url = "https://wewordle.org/gptapi/v1/web/get_limit"
58
+
59
+ # Initialize LitAgent for user agent generation
60
+ self.agent = LitAgent()
61
+
62
+ # Generate browser fingerprint
63
+ self.fingerprint = self.agent.generate_fingerprint(browser="chrome")
64
+
65
+ # Setup headers to mimic browser requests
66
+ self.headers = {
67
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
68
+ 'Accept': 'application/json',
69
+ 'Content-Type': 'application/json',
70
+ 'sec-ch-ua': '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
71
+ 'sec-ch-ua-mobile': '?0',
72
+ 'sec-ch-ua-platform': '"Windows"',
73
+ 'Referer': 'https://claude.online/',
74
+ 'Origin': 'https://claude.online',
75
+ 'Sec-Fetch-Dest': 'empty',
76
+ 'Sec-Fetch-Mode': 'cors',
77
+ 'Sec-Fetch-Site': 'cross-site',
78
+ }
79
+
80
+ # Initialize curl_cffi Session
81
+ self.session = Session()
82
+ self.session.headers.update(self.headers)
83
+ self.session.proxies = proxies
84
+
85
+ self.system_prompt = system_prompt
86
+ self.is_conversation = is_conversation
87
+ self.max_tokens_to_sample = max_tokens
88
+ self.timeout = timeout
89
+ self.last_response = {}
90
+ self.model = model
91
+
92
+ self.__available_optimizers = (
93
+ method
94
+ for method in dir(Optimizers)
95
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
96
+ )
97
+
98
+ Conversation.intro = (
99
+ AwesomePrompts().get_act(
100
+ act, raise_not_found=True, default=None, case_insensitive=True
101
+ )
102
+ if act
103
+ else intro or Conversation.intro
104
+ )
105
+
106
+ self.conversation = Conversation(
107
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
108
+ )
109
+ self.conversation.history_offset = history_offset
110
+
111
+ def _make_request(self, url: str, payload: Optional[Dict] = None, method: str = "POST") -> Dict:
112
+ """
113
+ Make a request to the API with error handling.
114
+
115
+ Args:
116
+ url: The URL to request
117
+ payload: Request payload (for POST requests)
118
+ method: HTTP method
119
+
120
+ Returns:
121
+ Parsed JSON response
122
+
123
+ Raises:
124
+ FailedToGenerateResponseError: If request fails
125
+ """
126
+ try:
127
+ if method == "POST":
128
+ response = self.session.post(
129
+ url,
130
+ json=payload,
131
+ timeout=self.timeout,
132
+ impersonate="chrome110"
133
+ )
134
+ else:
135
+ response = self.session.get(
136
+ url,
137
+ timeout=self.timeout,
138
+ impersonate="chrome110"
139
+ )
140
+
141
+ response.raise_for_status()
142
+
143
+ # Check rate limit headers
144
+ if 'ratelimit-remaining' in response.headers:
145
+ remaining = int(response.headers['ratelimit-remaining'])
146
+ if remaining <= 0:
147
+ reset_time = int(response.headers.get('ratelimit-reset', 60))
148
+ raise exceptions.FailedToGenerateResponseError(
149
+ f"Rate limit exceeded. Resets in {reset_time} seconds."
150
+ )
151
+
152
+ return response.json()
153
+
154
+ except CurlError as e:
155
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}")
156
+ except Exception as e:
157
+ raise exceptions.FailedToGenerateResponseError(f"Request failed ({type(e).__name__}): {str(e)}")
158
+
159
+ def get_remaining_limit(self) -> Dict[str, Union[int, float]]:
160
+ """
161
+ Get the current rate limit status.
162
+
163
+ Returns:
164
+ Dict containing limit information:
165
+ - limit: Remaining requests
166
+ - fullLimit: Unknown parameter (possibly daily limit)
167
+ """
168
+ try:
169
+ response = self._make_request(self.limit_url, method="GET")
170
+ return {
171
+ "limit": response.get("limit", 0),
172
+ "fullLimit": response.get("fullLimit", 0)
173
+ }
174
+ except Exception:
175
+ # Return default values if request fails
176
+ return {"limit": 0, "fullLimit": 0}
177
+
178
+ def create_message(self, content: str, role: str = "user") -> Dict[str, str]:
179
+ """
180
+ Create a message dictionary.
181
+
182
+ Args:
183
+ content: Message content
184
+ role: Message role (user/assistant)
185
+
186
+ Returns:
187
+ Message dictionary
188
+ """
189
+ return {
190
+ "content": content,
191
+ "role": role
192
+ }
193
+
194
+ def ask(
195
+ self,
196
+ prompt: str,
197
+ stream: bool = False,
198
+ raw: bool = False,
199
+ optimizer: str = None,
200
+ conversationally: bool = False,
201
+ ) -> Union[Dict[str, Any], Generator]:
202
+ """
203
+ Send a chat message and get response.
204
+
205
+ Args:
206
+ prompt: The message to send
207
+ stream: Whether to stream the response (not supported by this API)
208
+ raw: Whether to return raw response
209
+ optimizer: Optimizer to use for the prompt
210
+ conversationally: Whether to generate the prompt conversationally
211
+
212
+ Returns:
213
+ Dict containing response or Generator for streaming
214
+ """
215
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
216
+ if optimizer:
217
+ if optimizer in self.__available_optimizers:
218
+ conversation_prompt = getattr(Optimizers, optimizer)(
219
+ conversation_prompt if conversationally else prompt
220
+ )
221
+ else:
222
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
223
+
224
+ # Prepare messages (only current message supported by this API)
225
+ messages = [self.create_message(conversation_prompt, "user")]
226
+
227
+ payload = {
228
+ "messages": messages
229
+ }
230
+
231
+ def for_stream():
232
+ # This API doesn't support streaming, so simulate it
233
+ try:
234
+ response = self._make_request(self.chat_url, payload)
235
+
236
+ # Extract message content
237
+ if "message" in response and "content" in response["message"]:
238
+ content = response["message"]["content"]
239
+
240
+ # Simulate streaming by yielding chunks
241
+ words = content.split()
242
+ current_chunk = ""
243
+ for word in words:
244
+ current_chunk += word + " "
245
+ if len(current_chunk) > 50: # Yield chunks of ~50 characters
246
+ yield dict(text=current_chunk.strip())
247
+ current_chunk = ""
248
+ if current_chunk:
249
+ yield dict(text=current_chunk.strip())
250
+
251
+ self.last_response = {"text": content}
252
+ self.conversation.update_chat_history(prompt, content)
253
+ else:
254
+ raise exceptions.FailedToGenerateResponseError("Unexpected response format")
255
+
256
+ except Exception as e:
257
+ raise exceptions.FailedToGenerateResponseError(f"Chat request failed: {str(e)}")
258
+
259
+ def for_non_stream():
260
+ try:
261
+ response = self._make_request(self.chat_url, payload)
262
+
263
+ # Extract message content
264
+ if "message" in response and "content" in response["message"]:
265
+ content = response["message"]["content"]
266
+ self.last_response = {"text": content}
267
+ self.conversation.update_chat_history(prompt, content)
268
+ return self.last_response if not raw else content
269
+ else:
270
+ raise exceptions.FailedToGenerateResponseError("Unexpected response format")
271
+
272
+ except Exception as e:
273
+ raise exceptions.FailedToGenerateResponseError(f"Chat request failed: {str(e)}")
274
+
275
+ return for_stream() if stream else for_non_stream()
276
+
277
+ def chat(
278
+ self,
279
+ prompt: str,
280
+ stream: bool = False,
281
+ optimizer: str = None,
282
+ conversationally: bool = False,
283
+ ) -> Union[str, Generator[str, None, None]]:
284
+ """
285
+ Generate a response from Claude Online.
286
+
287
+ Args:
288
+ prompt: The prompt to send
289
+ stream: Whether to stream the response
290
+ optimizer: Optimizer to use
291
+ conversationally: Whether to generate conversationally
292
+
293
+ Returns:
294
+ Response string or generator for streaming
295
+ """
296
+ def for_stream_chat():
297
+ for response in self.ask(
298
+ prompt, stream=True, raw=False,
299
+ optimizer=optimizer, conversationally=conversationally
300
+ ):
301
+ yield self.get_message(response)
302
+
303
+ def for_non_stream_chat():
304
+ response_data = self.ask(
305
+ prompt, stream=False, raw=False,
306
+ optimizer=optimizer, conversationally=conversationally
307
+ )
308
+ return self.get_message(response_data)
309
+
310
+ return for_stream_chat() if stream else for_non_stream_chat()
311
+
312
+ def get_message(self, response: dict) -> str:
313
+ """
314
+ Extract message from response.
315
+
316
+ Args:
317
+ response: Response dictionary
318
+
319
+ Returns:
320
+ Message content
321
+ """
322
+ assert isinstance(response, dict), "Response should be of dict data-type only"
323
+ return response["text"]
324
+
325
+
326
+ if __name__ == "__main__":
327
+ print("-" * 80)
328
+ print(f"{'Model':<50} {'Status':<10} {'Response'}")
329
+ print("-" * 80)
330
+
331
+ try:
332
+ ai = ClaudeOnline(timeout=60)
333
+ limits = ai.get_remaining_limit()
334
+ print(f"Rate limits - Remaining: {limits['limit']}, Full limit: {limits['fullLimit']}")
335
+
336
+ if limits['limit'] > 0:
337
+ response = ai.chat("Say 'Hello World' in one word", stream=False)
338
+ if response and len(response.strip()) > 0:
339
+ status = "✓"
340
+ clean_text = response.strip().encode('utf-8', errors='ignore').decode('utf-8')
341
+ display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
342
+ else:
343
+ status = "✗"
344
+ display_text = "Empty response"
345
+ print(f"{'claude-online':<50} {status:<10} {display_text}")
346
+ else:
347
+ print(f"{'claude-online':<50} {'✗':<10} Rate limit exceeded")
348
+
349
+ except Exception as e:
350
+ print(f"{'claude-online':<50} {'✗':<10} {str(e)}")
@@ -0,0 +1,315 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ import tempfile
5
+ import time
6
+ from io import BytesIO
7
+ from typing import Optional
8
+
9
+ import requests
10
+ from requests.exceptions import RequestException
11
+
12
+ from webscout.litagent import LitAgent
13
+ from webscout.Provider.TTI.base import BaseImages, TTICompatibleProvider
14
+ from webscout.Provider.TTI.utils import ImageData, ImageResponse
15
+
16
+ try:
17
+ from PIL import Image
18
+ except ImportError:
19
+ Image = None
20
+
21
+
22
+ class Images(BaseImages):
23
+ def __init__(self, client):
24
+ self._client = client
25
+
26
+ def create(
27
+ self,
28
+ *,
29
+ model: str,
30
+ prompt: str,
31
+ n: int = 1,
32
+ size: str = "1024x1024",
33
+ response_format: str = "url",
34
+ user: Optional[str] = None,
35
+ style: str = "none",
36
+ aspect_ratio: str = "1:1",
37
+ timeout: int = 60,
38
+ image_format: str = "png",
39
+ seed: Optional[int] = None,
40
+ **kwargs,
41
+ ) -> ImageResponse:
42
+ """
43
+ Generate images using Claude Online's /imagine feature via Pollinations.ai.
44
+
45
+ Args:
46
+ model: Model to use (ignored, uses Pollinations.ai)
47
+ prompt: The image generation prompt
48
+ n: Number of images to generate (max 1 for Claude Online)
49
+ size: Image size (supports various sizes)
50
+ response_format: "url" or "b64_json"
51
+ timeout: Request timeout in seconds
52
+ image_format: Output format "png" or "jpeg"
53
+ **kwargs: Additional parameters
54
+
55
+ Returns:
56
+ ImageResponse with generated image data
57
+ """
58
+ if Image is None:
59
+ raise ImportError("Pillow (PIL) is required for image format conversion.")
60
+
61
+ # Claude Online only supports 1 image per request
62
+ if n > 1:
63
+ raise ValueError("Claude Online only supports generating 1 image per request")
64
+
65
+ # Parse size parameter
66
+ width, height = self._parse_size(size)
67
+
68
+ try:
69
+ # Clean the prompt (remove command words if present)
70
+ clean_prompt = self._clean_prompt(prompt)
71
+
72
+ # Generate image using Pollinations.ai API
73
+ timestamp = int(time.time() * 1000) # Use timestamp as seed for uniqueness
74
+ seed_value = seed if seed is not None else timestamp
75
+
76
+ # Build the Pollinations.ai URL
77
+ base_url = "https://image.pollinations.ai/prompt"
78
+ params = {
79
+ "width": width,
80
+ "height": height,
81
+ "nologo": "true",
82
+ "seed": seed_value
83
+ }
84
+
85
+ image_url = f"{base_url}/{clean_prompt}"
86
+ query_params = "&".join([f"{k}={v}" for k, v in params.items()])
87
+ full_image_url = f"{image_url}?{query_params}"
88
+
89
+ # Download the image
90
+ response = requests.get(full_image_url, timeout=timeout, stream=True)
91
+ response.raise_for_status()
92
+
93
+ img_bytes = response.content
94
+
95
+ # Convert image format if needed
96
+ with BytesIO(img_bytes) as input_io:
97
+ with Image.open(input_io) as im:
98
+ out_io = BytesIO()
99
+ if image_format.lower() == "jpeg":
100
+ im = im.convert("RGB")
101
+ im.save(out_io, format="JPEG")
102
+ else:
103
+ im.save(out_io, format="PNG")
104
+ processed_img_bytes = out_io.getvalue()
105
+
106
+ # Handle response format
107
+ if response_format == "url":
108
+ # Upload to image hosting service
109
+ uploaded_url = self._upload_image(processed_img_bytes, image_format)
110
+ if not uploaded_url:
111
+ raise RuntimeError("Failed to upload generated image")
112
+ result_data = [ImageData(url=uploaded_url)]
113
+ elif response_format == "b64_json":
114
+ b64 = base64.b64encode(processed_img_bytes).decode("utf-8")
115
+ result_data = [ImageData(b64_json=b64)]
116
+ else:
117
+ raise ValueError("response_format must be 'url' or 'b64_json'")
118
+
119
+ return ImageResponse(created=int(time.time()), data=result_data)
120
+
121
+ except RequestException as e:
122
+ raise RuntimeError(f"Failed to generate image with Claude Online: {e}")
123
+ except Exception as e:
124
+ raise RuntimeError(f"Unexpected error during image generation: {e}")
125
+
126
+ def _parse_size(self, size: str) -> tuple[int, int]:
127
+ """Parse size string into width and height."""
128
+ size = size.lower().strip()
129
+
130
+ # Handle common size formats
131
+ size_map = {
132
+ "256x256": (256, 256),
133
+ "512x512": (512, 512),
134
+ "1024x1024": (1024, 1024),
135
+ "1024x768": (1024, 768),
136
+ "768x1024": (768, 1024),
137
+ "1280x720": (1280, 720),
138
+ "720x1280": (720, 1280),
139
+ "1920x1080": (1920, 1080),
140
+ "1080x1920": (1080, 1920),
141
+ }
142
+
143
+ if size in size_map:
144
+ return size_map[size]
145
+
146
+ # Try to parse custom size (e.g., "800x600")
147
+ try:
148
+ width, height = size.split("x")
149
+ return int(width), int(height)
150
+ except (ValueError, AttributeError):
151
+ # Default to 1024x1024
152
+ return 1024, 1024
153
+
154
+ def _clean_prompt(self, prompt: str) -> str:
155
+ """Clean the prompt by removing command prefixes."""
156
+ # Remove common image generation command prefixes
157
+ prefixes_to_remove = [
158
+ r'^/imagine\s*',
159
+ r'^/image\s*',
160
+ r'^/picture\s*',
161
+ r'^/draw\s*',
162
+ r'^/create\s*',
163
+ r'^/generate\s*',
164
+ r'^создай изображение\s*',
165
+ r'^нарисуй\s*',
166
+ r'^сгенерируй картинку\s*',
167
+ ]
168
+
169
+ import re
170
+ clean_prompt = prompt
171
+ for prefix in prefixes_to_remove:
172
+ clean_prompt = re.sub(prefix, '', clean_prompt, flags=re.IGNORECASE)
173
+
174
+ return clean_prompt.strip()
175
+
176
+ def _upload_image(self, img_bytes: bytes, image_format: str, max_retries: int = 3) -> Optional[str]:
177
+ """Upload image to hosting service and return URL"""
178
+
179
+ def upload_to_catbox(img_bytes, image_format):
180
+ """Upload to catbox.moe"""
181
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
182
+ tmp_path = None
183
+ try:
184
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
185
+ tmp.write(img_bytes)
186
+ tmp.flush()
187
+ tmp_path = tmp.name
188
+
189
+ with open(tmp_path, "rb") as f:
190
+ files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
191
+ data = {"reqtype": "fileupload", "json": "true"}
192
+ headers = {"User-Agent": LitAgent().random()}
193
+
194
+ resp = requests.post(
195
+ "https://catbox.moe/user/api.php",
196
+ files=files,
197
+ data=data,
198
+ headers=headers,
199
+ timeout=30,
200
+ )
201
+
202
+ if resp.status_code == 200 and resp.text.strip():
203
+ text = resp.text.strip()
204
+ if text.startswith("http"):
205
+ return text
206
+ try:
207
+ result = resp.json()
208
+ if "url" in result:
209
+ return result["url"]
210
+ except json.JSONDecodeError:
211
+ pass
212
+ except Exception:
213
+ pass
214
+ finally:
215
+ if tmp_path and os.path.isfile(tmp_path):
216
+ try:
217
+ os.remove(tmp_path)
218
+ except Exception:
219
+ pass
220
+ return None
221
+
222
+ def upload_to_0x0(img_bytes, image_format):
223
+ """Upload to 0x0.st as fallback"""
224
+ ext = "jpg" if image_format.lower() == "jpeg" else "png"
225
+ tmp_path = None
226
+ try:
227
+ with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
228
+ tmp.write(img_bytes)
229
+ tmp.flush()
230
+ tmp_path = tmp.name
231
+
232
+ with open(tmp_path, "rb") as img_file:
233
+ files = {"file": img_file}
234
+ response = requests.post("https://0x0.st", files=files, timeout=30)
235
+ response.raise_for_status()
236
+ image_url = response.text.strip()
237
+ if image_url.startswith("http"):
238
+ return image_url
239
+ except Exception:
240
+ pass
241
+ finally:
242
+ if tmp_path and os.path.isfile(tmp_path):
243
+ try:
244
+ os.remove(tmp_path)
245
+ except Exception:
246
+ pass
247
+ return None
248
+
249
+ # Try primary upload method
250
+ for attempt in range(max_retries):
251
+ uploaded_url = upload_to_catbox(img_bytes, image_format)
252
+ if uploaded_url:
253
+ return uploaded_url
254
+ time.sleep(1 * (attempt + 1))
255
+
256
+ # Try fallback method
257
+ for attempt in range(max_retries):
258
+ uploaded_url = upload_to_0x0(img_bytes, image_format)
259
+ if uploaded_url:
260
+ return uploaded_url
261
+ time.sleep(1 * (attempt + 1))
262
+
263
+ return None
264
+
265
+
266
+ class ClaudeOnlineTTI(TTICompatibleProvider):
267
+ """
268
+ Claude Online Text-to-Image Provider
269
+
270
+ Uses Claude Online's /imagine feature with Pollinations.ai backend.
271
+ Supports high-quality image generation with various styles and sizes.
272
+ """
273
+
274
+ AVAILABLE_MODELS = ["claude-imagine"]
275
+
276
+ def __init__(self):
277
+ self.api_endpoint = "https://image.pollinations.ai/prompt"
278
+ self.session = requests.Session()
279
+ self.user_agent = LitAgent().random()
280
+ self.headers = {
281
+ "accept": "image/*",
282
+ "accept-language": "en-US,en;q=0.9",
283
+ "user-agent": self.user_agent,
284
+ }
285
+ self.session.headers.update(self.headers)
286
+ self.images = Images(self)
287
+
288
+ @property
289
+ def models(self):
290
+ class _ModelList:
291
+ def list(inner_self):
292
+ return type(self).AVAILABLE_MODELS
293
+
294
+ return _ModelList()
295
+
296
+
297
+ if __name__ == "__main__":
298
+ from rich import print
299
+
300
+ # Test the Claude Online TTI provider
301
+ client = ClaudeOnlineTTI()
302
+
303
+ try:
304
+ response = client.images.create(
305
+ model="claude-imagine",
306
+ prompt="a beautiful sunset over mountains with vibrant colors",
307
+ response_format="url",
308
+ timeout=60,
309
+ )
310
+ print("✅ Image generation successful!")
311
+ print(response)
312
+ except Exception as e:
313
+ print(f"❌ Image generation failed: {e}")
314
+ import traceback
315
+ traceback.print_exc()
webscout/version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2025.10.16"
1
+ __version__ = "2025.10.17"
2
2
  __prog__ = "webscout"
webscout/version.py.bak CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "2025.10.15"
1
+ __version__ = "2025.10.16"
2
2
  __prog__ = "webscout"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webscout
3
- Version: 2025.10.16
3
+ Version: 2025.10.17
4
4
  Summary: Search for anything using Google, DuckDuckGo, phind.com, Contains AI models, can transcribe yt videos, temporary email and phone number generation, has TTS support, webai (terminal gpt and open interpreter) and offline LLMs and more
5
5
  Author-email: OEvortex <helpingai5@gmail.com>
6
6
  License: HelpingAI
@@ -50,7 +50,6 @@ Requires-Dist: lxml>=5.2.2
50
50
  Requires-Dist: orjson
51
51
  Requires-Dist: PyYAML
52
52
  Requires-Dist: pillow
53
- Requires-Dist: bson
54
53
  Requires-Dist: cloudscraper
55
54
  Requires-Dist: html5lib
56
55
  Requires-Dist: aiofiles
@@ -163,7 +162,7 @@ Dynamic: license-file
163
162
  - **[YouTube Toolkit](webscout/Extra/YTToolkit/README.md):** Advanced YouTube video and transcript management with multi-language support
164
163
  - **[Text-to-Speech (TTS)](webscout/Provider/TTS/README.md):** Convert text into natural-sounding speech using multiple AI-powered providers
165
164
  - **[Text-to-Image](webscout/Provider/TTI/README.md):** Generate high-quality images using a wide range of AI art providers
166
- - **[Weather Tools](webscout/Extra/weather.md):** Retrieve detailed weather information for any location
165
+ - **[Weather Tools](docs/weather.md):** Retrieve detailed weather information for any location
167
166
  </p>
168
167
  </details>
169
168
 
@@ -14,13 +14,12 @@ webscout/prompt_manager.py,sha256=ysKFgPhkV3uqrOCilqcS9rG8xhzdU_d2wx0grC9WCCc,98
14
14
  webscout/sanitize.py,sha256=pw2Dzn-Jw9mOD4mpALYAvAf-medA-9AqdzsOmdXQbl0,46577
15
15
  webscout/update_checker.py,sha256=bz0TzRxip9DOIVMFyNz9HsGj4RKB0xZgo57AUVSJINo,3708
16
16
  webscout/utils.py,sha256=o2hU3qaVPk25sog3e4cyVZO3l8xwaZpYRziZPotEzNo,3075
17
- webscout/version.py,sha256=bau6psbS6y1YovrRr6K7N3uBj_p0yf9WmHDqSK16bMs,51
18
- webscout/version.py.bak,sha256=lFdrFaHswFMDr9VdMdSSpxeTuXjC5Z2DIrPJbWo9KtQ,51
17
+ webscout/version.py,sha256=3D9mFWY4P-ZGCKSuDPkkzHusmyndScdoNbUtry1vdCE,51
18
+ webscout/version.py.bak,sha256=bau6psbS6y1YovrRr6K7N3uBj_p0yf9WmHDqSK16bMs,51
19
19
  webscout/Extra/Act.md,sha256=_C2VW_Dc-dc7eejpGYKAOZhImHKPiQ7NSwE3bkzr6fg,18952
20
20
  webscout/Extra/__init__.py,sha256=KvJRsRBRO-fZp2jSCl6KQnPppi93hriA6O_U1O1s31c,177
21
21
  webscout/Extra/gguf.md,sha256=McXGz5sTfzOO9X4mH8yIqu5K3CgjzyXKi4_HQtezdZ4,12435
22
22
  webscout/Extra/gguf.py,sha256=sbUsEPbws99yzS2EzZHLmZ55q6esFfu7k2y5WzMBEbA,55683
23
- webscout/Extra/weather.md,sha256=CaDwLmmHEXHSsMkSxFzrNOl2YslRcIMn3Hd3N1eaFq4,9781
24
23
  webscout/Extra/weather.py,sha256=KwbJC2Most6jVLvv8X_Ct7APGimSr98REgLc07kR-Ec,7648
25
24
  webscout/Extra/weather_ascii.py,sha256=k6U0F9w5xXPvYK-Wq3pjUh7W0gGJG4e_YhxeUI65Vpk,2612
26
25
  webscout/Extra/GitToolkit/__init__.py,sha256=S8clC97xkihcsxfbAO9QOmSO2ljLOiQFoUh_klhp2zw,146
@@ -68,6 +67,7 @@ webscout/Provider/Andi.py,sha256=CWFMRw9edQ0yRGLXtciPsl_O0v2Qs80TH8TkJ2bkAUk,873
68
67
  webscout/Provider/Apriel.py,sha256=caAjFQtMLfB0Er_eA-tW8_kGASE5AnaqLYifw1uJuIo,11335
69
68
  webscout/Provider/ChatGPTClone.py,sha256=hX_9Ytegiz6oDARzjwU11PPEXJqwTFGHAgShtDLbGHs,9715
70
69
  webscout/Provider/ChatSandbox.py,sha256=Hl8vOQzij7VyYVoL3DvJO6HGUs6tXZY3xrbCLKrF_ZI,13122
70
+ webscout/Provider/ClaudeOnline.py,sha256=3J5LEjvxzpYgIcycCq1aG_kFjks7ECkJS6l0HQ5bEyQ,12748
71
71
  webscout/Provider/Cloudflare.py,sha256=nrHCZ9SYNNRIxxzR_QRU1fy-jh31WnErxIimF0aDZms,14155
72
72
  webscout/Provider/Cohere.py,sha256=wPULeG_2JZdhN8oTBjs_QNqs6atjkYkjCa01mRmg8Fw,8082
73
73
  webscout/Provider/Deepinfra.py,sha256=Z3FNMaaVd4KiitDG8LBgGWycNuT6Y1Z06sCFURd0Ynw,15882
@@ -193,6 +193,7 @@ webscout/Provider/TTI/__init__.py,sha256=EwtLtQs6VEpLFOBeXi9hC9FTJIJdHPZTZtu3iFF
193
193
  webscout/Provider/TTI/aiarta.py,sha256=aWIATT_xQU7AmH3rK9mDCrUyN_7yOqkpXFPKicrGKfY,14956
194
194
  webscout/Provider/TTI/base.py,sha256=SnNs-mJH8tcmcygNnfNtwPvWBk11FUO6nLTfnHnZADQ,5088
195
195
  webscout/Provider/TTI/bing.py,sha256=wzUtAprlptlvuA3wndn3i-5UBQIL-YSe8rmjhqb1T-Q,10632
196
+ webscout/Provider/TTI/claudeonline.py,sha256=v6CEst37QG53g-H9rqjwiZeipJyedU1HHG3PrHX31Bw,10969
196
197
  webscout/Provider/TTI/gpt1image.py,sha256=PKqc0JU7hweypnSooI7gWuXqJbKz52xfGQny90-Uin0,4831
197
198
  webscout/Provider/TTI/imagen.py,sha256=VtSTJzTao4oUje4viEuYbcAw2Fol-JhBPax3XeXuByM,6488
198
199
  webscout/Provider/TTI/infip.py,sha256=qAzcym5hyxHyuW5PFqcWN__ebs5s9wLCADakO_28aD4,7083
@@ -328,9 +329,9 @@ webscout/zeroart/__init__.py,sha256=Cy9AUtXnOaFBQjNvCpN19IXJo7Lg15VTaNcTBxOTFek,
328
329
  webscout/zeroart/base.py,sha256=I-xhDEfArBb6q7hiF5oPoyXeu2hzL6orp7uWgS_YtG8,2299
329
330
  webscout/zeroart/effects.py,sha256=XUNZY1-wMPd6GNL3glFXtWaF9wDis_z55qTyCdnzHDo,5063
330
331
  webscout/zeroart/fonts.py,sha256=S7qDhUmDXl1makMreZl_eVW_7-sqVQiGn-kQKl0Hg_A,51006
331
- webscout-2025.10.16.dist-info/licenses/LICENSE.md,sha256=hyfFlVn7pWcrvuvs-piB8k4J8DlXdOsYje9RyPxc6Ik,7543
332
- webscout-2025.10.16.dist-info/METADATA,sha256=1jpCGVNGZrYgLltqLS8-vUlre_bLwQdq_O5XT4XljKQ,21690
333
- webscout-2025.10.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
334
- webscout-2025.10.16.dist-info/entry_points.txt,sha256=4xAgKHWwNhAvJyShLCFs_IU8Reb8zR3wqf8egrsDr8g,118
335
- webscout-2025.10.16.dist-info/top_level.txt,sha256=nYIw7OKBQDr_Z33IzZUKidRD3zQEo8jOJYkMVMeN334,9
336
- webscout-2025.10.16.dist-info/RECORD,,
332
+ webscout-2025.10.17.dist-info/licenses/LICENSE.md,sha256=hyfFlVn7pWcrvuvs-piB8k4J8DlXdOsYje9RyPxc6Ik,7543
333
+ webscout-2025.10.17.dist-info/METADATA,sha256=-12NEKcAbsCMUnFV4pl40tKrXhXDW0skcWCfCA1vhG8,21660
334
+ webscout-2025.10.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
335
+ webscout-2025.10.17.dist-info/entry_points.txt,sha256=4xAgKHWwNhAvJyShLCFs_IU8Reb8zR3wqf8egrsDr8g,118
336
+ webscout-2025.10.17.dist-info/top_level.txt,sha256=nYIw7OKBQDr_Z33IzZUKidRD3zQEo8jOJYkMVMeN334,9
337
+ webscout-2025.10.17.dist-info/RECORD,,
webscout/Extra/weather.md DELETED
@@ -1,281 +0,0 @@
1
- <div align="center">
2
- <h1>☀️ Webscout Weather Toolkit</h1>
3
- <p><strong>Comprehensive weather data retrieval and visualization tools</strong></p>
4
-
5
- <!-- Badges -->
6
- <p>
7
- <a href="#-installation"><img src="https://img.shields.io/badge/Easy-Installation-success?style=flat-square" alt="Easy Installation"></a>
8
- <a href="#-current-weather-data"><img src="https://img.shields.io/badge/Real--time-Data-blue?style=flat-square" alt="Real-time Data"></a>
9
- <a href="#-ascii-art-weather"><img src="https://img.shields.io/badge/ASCII-Visualization-orange?style=flat-square" alt="ASCII Visualization"></a>
10
- </p>
11
- </div>
12
-
13
- > [!NOTE]
14
- > Webscout's Weather Toolkit provides powerful tools to retrieve and display weather information in various formats, including structured data and ASCII art visualization.
15
-
16
- ## 📋 Table of Contents
17
-
18
- - [🚀 Installation](#-installation)
19
- - [🌡️ Current Weather Data](#-current-weather-data)
20
- - [🔮 Weather Forecast](#-weather-forecast)
21
- - [🎨 ASCII Art Weather](#-ascii-art-weather)
22
- - [📊 Data Structure](#-data-structure)
23
- - [⚙️ Advanced Usage](#-advanced-usage)
24
-
25
- ## 🚀 Installation
26
-
27
- The Weather Toolkit is included in the Webscout package. Install or update Webscout to get access:
28
-
29
- ```bash
30
- pip install -U webscout
31
- ```
32
-
33
- ## 🌡️ Current Weather Data
34
-
35
- Retrieve structured current weather information for any location worldwide.
36
-
37
- ```python
38
- from webscout.Extra import weather
39
-
40
- # Get weather for a specific location
41
- forecast = weather.get("London")
42
-
43
- # Access current conditions
44
- print(f"Current weather: {forecast.summary}")
45
-
46
- # Access temperature details
47
- if forecast.current_condition:
48
- print(f"Temperature: {forecast.current_condition.temp_c}°C / {forecast.current_condition.temp_f}°F")
49
- print(f"Feels like: {forecast.current_condition.feels_like_c}°C")
50
- print(f"Conditions: {forecast.current_condition.weather_desc}")
51
- print(f"Wind: {forecast.current_condition.wind_speed_kmph} km/h from {forecast.current_condition.wind_direction}")
52
- print(f"Humidity: {forecast.current_condition.humidity}%")
53
- print(f"Visibility: {forecast.current_condition.visibility} km")
54
- print(f"Pressure: {forecast.current_condition.pressure} mb")
55
- ```
56
-
57
- ## 🔮 Weather Forecast
58
-
59
- Access detailed forecast information for today and upcoming days.
60
-
61
- ```python
62
- from webscout.Extra import weather
63
-
64
- forecast = weather.get("Tokyo")
65
-
66
- # Today's forecast
67
- if forecast.today:
68
- print(f"Today ({forecast.today.date_formatted}):")
69
- print(f" Temperature range: {forecast.today.min_temp_c}°C - {forecast.today.max_temp_c}°C")
70
- print(f" Sunrise: {forecast.today.sunrise}, Sunset: {forecast.today.sunset}")
71
-
72
- # Access hourly forecasts
73
- if forecast.today.hourly and len(forecast.today.hourly) > 4:
74
- noon = forecast.today.hourly[4] # Noon (12:00) is usually index 4
75
- print(f" Noon conditions: {noon.weather_desc}")
76
- print(f" Chance of rain: {noon.chance_of_rain}%")
77
- print(f" Humidity: {noon.humidity}%")
78
- print(f" UV Index: {noon.uv_index}")
79
-
80
- # Tomorrow's forecast
81
- if forecast.tomorrow:
82
- print(f"\nTomorrow ({forecast.tomorrow.date_formatted}):")
83
- print(f" Temperature range: {forecast.tomorrow.min_temp_c}°C - {forecast.tomorrow.max_temp_c}°C")
84
- print(f" Weather: {forecast.tomorrow.weather_desc}")
85
- print(f" Sunrise: {forecast.tomorrow.sunrise}, Sunset: {forecast.tomorrow.sunset}")
86
-
87
- # Extended forecast (next 3 days)
88
- if forecast.days and len(forecast.days) > 2:
89
- print("\nExtended Forecast:")
90
- for day in forecast.days[2:5]: # Days 3-5
91
- print(f" {day.date_formatted}: {day.min_temp_c}°C - {day.max_temp_c}°C, {day.weather_desc}")
92
- ```
93
-
94
- ## 🎨 ASCII Art Weather
95
-
96
- Retrieve weather information as visually appealing ASCII art.
97
-
98
- ```python
99
- from webscout.Extra import weather_ascii
100
-
101
- # Get ASCII art weather
102
- result = weather_ascii.get("Paris")
103
-
104
- # Display the ASCII art weather
105
- print(result.content)
106
-
107
- # Get ASCII art with temperature in Fahrenheit
108
- result_f = weather_ascii.get("New York", units="imperial")
109
- print(result_f.content)
110
-
111
- # Get ASCII art with a specific number of forecast days
112
- result_days = weather_ascii.get("Berlin", days=3)
113
- print(result_days.content)
114
- ```
115
-
116
- Example output:
117
- ```
118
- Weather for Paris, France
119
-
120
- \ / Clear
121
- .-. +20°C
122
- ― ( ) ― ↗ 11 km/h
123
- `-' 10 km
124
- / \ 0.0 mm
125
- ┌─────────────┐
126
- ┌───────────────────────┤ Wed 14 Apr ├───────────────────────┐
127
- │ Morning └──────┬──────┘ Evening Night │
128
- ├──────────────────────────────┼──────────────────────────────┤
129
- │ Cloudy │ Clear │
130
- │ .--. +20..+22 °C │ \ / +15 °C │
131
- │ .-( ). ↗ 11-13 km/h │ .-. ↗ 7-9 km/h │
132
- │ (___.__)__) 10 km │ ― ( ) ― 10 km │
133
- │ 0.0 mm | 0% │ `-' 0.0 mm | 0% │
134
- └──────────────────────────────┴──────────────────────────────┘
135
- ```
136
-
137
- ## 📊 Data Structure
138
-
139
- The Weather Toolkit returns structured data objects with the following key components:
140
-
141
- <details>
142
- <summary><strong>Forecast Object Structure</strong></summary>
143
-
144
- ```python
145
- forecast = weather.get("London")
146
-
147
- # Main forecast object attributes
148
- forecast.location # Location information (city, country, etc.)
149
- forecast.summary # Brief summary of current weather
150
- forecast.current_condition # Current weather conditions
151
- forecast.today # Today's forecast
152
- forecast.tomorrow # Tomorrow's forecast
153
- forecast.days # List of daily forecasts (including today and tomorrow)
154
-
155
- # Current condition attributes
156
- current = forecast.current_condition
157
- current.temp_c # Temperature in Celsius
158
- current.temp_f # Temperature in Fahrenheit
159
- current.feels_like_c # "Feels like" temperature in Celsius
160
- current.feels_like_f # "Feels like" temperature in Fahrenheit
161
- current.wind_speed_kmph # Wind speed in km/h
162
- current.wind_speed_mph # Wind speed in mph
163
- current.wind_direction # Wind direction (e.g., "NW")
164
- current.humidity # Humidity percentage
165
- current.pressure # Atmospheric pressure in millibars
166
- current.visibility # Visibility in kilometers
167
- current.weather_desc # Weather description (e.g., "Partly cloudy")
168
- current.weather_code # Weather code for icon mapping
169
-
170
- # Daily forecast attributes
171
- day = forecast.today # or any day from forecast.days
172
- day.date # Date (YYYY-MM-DD)
173
- day.date_formatted # Formatted date (e.g., "Wed 14 Apr")
174
- day.max_temp_c # Maximum temperature in Celsius
175
- day.min_temp_c # Minimum temperature in Celsius
176
- day.max_temp_f # Maximum temperature in Fahrenheit
177
- day.min_temp_f # Minimum temperature in Fahrenheit
178
- day.sunrise # Sunrise time
179
- day.sunset # Sunset time
180
- day.weather_desc # Weather description
181
- day.weather_code # Weather code
182
- day.hourly # List of hourly forecasts for this day
183
- ```
184
- </details>
185
-
186
- <details>
187
- <summary><strong>ASCII Weather Object Structure</strong></summary>
188
-
189
- ```python
190
- result = weather_ascii.get("Paris")
191
-
192
- # ASCII result attributes
193
- result.content # The full ASCII art weather display
194
- result.location # Location information
195
- result.temperature # Current temperature
196
- result.conditions # Current weather conditions
197
- result.forecast_days # Number of forecast days included
198
- ```
199
- </details>
200
-
201
- ## ⚙️ Advanced Usage
202
-
203
- <details>
204
- <summary><strong>Custom Location Formats</strong></summary>
205
-
206
- The Weather Toolkit supports various location formats:
207
-
208
- ```python
209
- # City name
210
- weather.get("London")
211
-
212
- # City and country
213
- weather.get("Paris, France")
214
-
215
- # ZIP/Postal code (US)
216
- weather.get("10001") # New York, NY
217
-
218
- # Latitude and Longitude
219
- weather.get("40.7128,-74.0060") # New York coordinates
220
- ```
221
- </details>
222
-
223
- <details>
224
- <summary><strong>Temperature Units</strong></summary>
225
-
226
- Control temperature units in ASCII weather display:
227
-
228
- ```python
229
- # Default (metric - Celsius)
230
- weather_ascii.get("Tokyo")
231
-
232
- # Imperial (Fahrenheit)
233
- weather_ascii.get("New York", units="imperial")
234
-
235
- # Metric (Celsius)
236
- weather_ascii.get("Berlin", units="metric")
237
- ```
238
- </details>
239
-
240
- <details>
241
- <summary><strong>Forecast Days</strong></summary>
242
-
243
- Control the number of forecast days in ASCII weather display:
244
-
245
- ```python
246
- # Default (1 day)
247
- weather_ascii.get("Sydney")
248
-
249
- # 3-day forecast
250
- weather_ascii.get("Rio de Janeiro", days=3)
251
-
252
- # 5-day forecast (maximum)
253
- weather_ascii.get("Moscow", days=5)
254
- ```
255
- </details>
256
-
257
- <details>
258
- <summary><strong>Error Handling</strong></summary>
259
-
260
- Implement proper error handling for robust applications:
261
-
262
- ```python
263
- from webscout.Extra import weather
264
- from webscout.exceptions import APIError
265
-
266
- try:
267
- forecast = weather.get("London")
268
- print(f"Current temperature: {forecast.current_condition.temp_c}°C")
269
- except APIError as e:
270
- print(f"API Error: {e}")
271
- except Exception as e:
272
- print(f"An error occurred: {e}")
273
- ```
274
- </details>
275
-
276
- <div align="center">
277
- <p>
278
- <a href="https://github.com/OEvortex/Webscout"><img alt="GitHub Repository" src="https://img.shields.io/badge/GitHub-Repository-181717?style=for-the-badge&logo=github&logoColor=white"></a>
279
- <a href="https://t.me/PyscoutAI"><img alt="Telegram Group" src="https://img.shields.io/badge/Telegram%20Group-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white"></a>
280
- </p>
281
- </div>