webscout 8.3__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.
- webscout/AIauto.py +4 -4
- webscout/AIbase.py +61 -1
- webscout/AIutel.py +46 -53
- webscout/Bing_search.py +418 -0
- webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
- webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
- webscout/Extra/YTToolkit/ytapi/video.py +10 -10
- webscout/Extra/autocoder/autocoder_utiles.py +1 -1
- webscout/Extra/gguf.py +706 -177
- webscout/Litlogger/formats.py +9 -0
- webscout/Litlogger/handlers.py +18 -0
- webscout/Litlogger/logger.py +43 -1
- webscout/Provider/AISEARCH/genspark_search.py +7 -7
- webscout/Provider/AISEARCH/scira_search.py +3 -2
- webscout/Provider/GeminiProxy.py +140 -0
- webscout/Provider/LambdaChat.py +7 -1
- webscout/Provider/MCPCore.py +78 -75
- webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
- webscout/Provider/OPENAI/GeminiProxy.py +328 -0
- webscout/Provider/OPENAI/Qwen3.py +303 -303
- webscout/Provider/OPENAI/README.md +5 -0
- webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
- webscout/Provider/OPENAI/TogetherAI.py +355 -0
- webscout/Provider/OPENAI/__init__.py +16 -1
- webscout/Provider/OPENAI/autoproxy.py +332 -0
- webscout/Provider/OPENAI/base.py +101 -14
- webscout/Provider/OPENAI/chatgpt.py +15 -2
- webscout/Provider/OPENAI/chatgptclone.py +14 -3
- webscout/Provider/OPENAI/deepinfra.py +339 -328
- webscout/Provider/OPENAI/e2b.py +295 -74
- webscout/Provider/OPENAI/mcpcore.py +109 -70
- webscout/Provider/OPENAI/opkfc.py +18 -6
- webscout/Provider/OPENAI/scirachat.py +59 -50
- webscout/Provider/OPENAI/toolbaz.py +2 -10
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +367 -367
- webscout/Provider/OPENAI/xenai.py +514 -0
- webscout/Provider/OPENAI/yep.py +389 -383
- webscout/Provider/STT/__init__.py +3 -0
- webscout/Provider/STT/base.py +281 -0
- webscout/Provider/STT/elevenlabs.py +265 -0
- webscout/Provider/TTI/__init__.py +4 -1
- webscout/Provider/TTI/aiarta.py +399 -365
- webscout/Provider/TTI/base.py +74 -2
- webscout/Provider/TTI/bing.py +231 -0
- webscout/Provider/TTI/fastflux.py +63 -30
- webscout/Provider/TTI/gpt1image.py +149 -0
- webscout/Provider/TTI/imagen.py +196 -0
- webscout/Provider/TTI/magicstudio.py +60 -29
- webscout/Provider/TTI/piclumen.py +43 -32
- webscout/Provider/TTI/pixelmuse.py +232 -225
- webscout/Provider/TTI/pollinations.py +43 -32
- webscout/Provider/TTI/together.py +287 -0
- webscout/Provider/TTI/utils.py +2 -1
- webscout/Provider/TTS/README.md +1 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/freetts.py +140 -0
- webscout/Provider/TTS/speechma.py +45 -39
- webscout/Provider/TogetherAI.py +366 -0
- webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
- webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
- webscout/Provider/XenAI.py +324 -0
- webscout/Provider/__init__.py +8 -0
- webscout/Provider/deepseek_assistant.py +378 -0
- webscout/Provider/scira_chat.py +3 -2
- webscout/Provider/toolbaz.py +0 -1
- webscout/auth/__init__.py +44 -0
- webscout/auth/api_key_manager.py +189 -0
- webscout/auth/auth_system.py +100 -0
- webscout/auth/config.py +76 -0
- webscout/auth/database.py +400 -0
- webscout/auth/exceptions.py +67 -0
- webscout/auth/middleware.py +248 -0
- webscout/auth/models.py +130 -0
- webscout/auth/providers.py +257 -0
- webscout/auth/rate_limiter.py +254 -0
- webscout/auth/request_models.py +127 -0
- webscout/auth/request_processing.py +226 -0
- webscout/auth/routes.py +526 -0
- webscout/auth/schemas.py +103 -0
- webscout/auth/server.py +312 -0
- webscout/auth/static/favicon.svg +11 -0
- webscout/auth/swagger_ui.py +203 -0
- webscout/auth/templates/components/authentication.html +237 -0
- webscout/auth/templates/components/base.html +103 -0
- webscout/auth/templates/components/endpoints.html +750 -0
- webscout/auth/templates/components/examples.html +491 -0
- webscout/auth/templates/components/footer.html +75 -0
- webscout/auth/templates/components/header.html +27 -0
- webscout/auth/templates/components/models.html +286 -0
- webscout/auth/templates/components/navigation.html +70 -0
- webscout/auth/templates/static/api.js +455 -0
- webscout/auth/templates/static/icons.js +168 -0
- webscout/auth/templates/static/main.js +784 -0
- webscout/auth/templates/static/particles.js +201 -0
- webscout/auth/templates/static/styles.css +3353 -0
- webscout/auth/templates/static/ui.js +374 -0
- webscout/auth/templates/swagger_ui.html +170 -0
- webscout/client.py +49 -3
- webscout/litagent/Readme.md +12 -3
- webscout/litagent/agent.py +99 -62
- webscout/scout/core/scout.py +104 -26
- webscout/scout/element.py +139 -18
- webscout/swiftcli/core/cli.py +14 -3
- webscout/swiftcli/decorators/output.py +59 -9
- webscout/update_checker.py +31 -49
- webscout/version.py +1 -1
- webscout/webscout_search.py +4 -12
- webscout/webscout_search_async.py +3 -10
- webscout/yep_search.py +2 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/OPENAI/api.py +0 -1035
- webscout/Provider/TTI/artbit.py +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
webscout/scout/element.py
CHANGED
|
@@ -151,7 +151,7 @@ class Tag:
|
|
|
151
151
|
"""
|
|
152
152
|
return hash((self.name, frozenset(self.attrs.items()), str(self)))
|
|
153
153
|
|
|
154
|
-
def find(self, name=None, attrs={}, recursive=True, text=None, **kwargs) -> Optional['Tag']:
|
|
154
|
+
def find(self, name=None, attrs={}, recursive=True, text=None, limit=None, class_=None, **kwargs) -> Optional['Tag']:
|
|
155
155
|
"""
|
|
156
156
|
Find the first matching child element.
|
|
157
157
|
Enhanced with more flexible matching.
|
|
@@ -165,10 +165,26 @@ class Tag:
|
|
|
165
165
|
Returns:
|
|
166
166
|
Tag or None: First matching element
|
|
167
167
|
"""
|
|
168
|
+
# Merge class_ with attrs['class'] if both are present
|
|
169
|
+
attrs = dict(attrs) if attrs else {}
|
|
170
|
+
if class_ is not None:
|
|
171
|
+
if 'class' in attrs:
|
|
172
|
+
# Merge both
|
|
173
|
+
if isinstance(attrs['class'], list):
|
|
174
|
+
class_list = attrs['class']
|
|
175
|
+
else:
|
|
176
|
+
class_list = [cls.strip() for cls in re.split(r'[ ,]+', str(attrs['class'])) if cls.strip()]
|
|
177
|
+
if isinstance(class_, list):
|
|
178
|
+
class_list += class_
|
|
179
|
+
else:
|
|
180
|
+
class_list += [cls.strip() for cls in re.split(r'[ ,]+', str(class_)) if cls.strip()]
|
|
181
|
+
attrs['class'] = class_list
|
|
182
|
+
else:
|
|
183
|
+
attrs['class'] = class_
|
|
168
184
|
results = self.find_all(name, attrs, recursive, text, limit=1, **kwargs)
|
|
169
185
|
return results[0] if results else None
|
|
170
186
|
|
|
171
|
-
def find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs) -> List['Tag']:
|
|
187
|
+
def find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, class_=None, **kwargs) -> List['Tag']:
|
|
172
188
|
"""
|
|
173
189
|
Find all matching child elements.
|
|
174
190
|
Enhanced with more flexible matching and BeautifulSoup-like features.
|
|
@@ -197,12 +213,17 @@ class Tag:
|
|
|
197
213
|
|
|
198
214
|
# Check attributes with more flexible matching
|
|
199
215
|
for k, v in attrs.items():
|
|
200
|
-
# Handle special attribute matching
|
|
201
216
|
if k == 'class':
|
|
202
217
|
tag_classes = tag.get('class', [])
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
218
|
+
# Support multiple classes separated by space or comma
|
|
219
|
+
if isinstance(v, str):
|
|
220
|
+
v_classes = [cls.strip() for cls in re.split(r'[ ,]+', v) if cls.strip()]
|
|
221
|
+
if not all(cls in tag_classes for cls in v_classes):
|
|
222
|
+
return False
|
|
223
|
+
elif isinstance(v, list):
|
|
224
|
+
if not all(cls in tag_classes for cls in v):
|
|
225
|
+
return False
|
|
226
|
+
else:
|
|
206
227
|
return False
|
|
207
228
|
elif k == 'id':
|
|
208
229
|
if tag.get('id') != v:
|
|
@@ -412,31 +433,131 @@ class Tag:
|
|
|
412
433
|
"""Remove all contents of the tag."""
|
|
413
434
|
self.contents.clear()
|
|
414
435
|
|
|
436
|
+
@property
|
|
437
|
+
def string(self):
|
|
438
|
+
"""
|
|
439
|
+
Get the string content of the tag.
|
|
440
|
+
Returns the combined text of the tag's contents.
|
|
441
|
+
"""
|
|
442
|
+
return self.get_text()
|
|
443
|
+
|
|
444
|
+
@string.setter
|
|
445
|
+
def string(self, value):
|
|
446
|
+
"""
|
|
447
|
+
Set the string content of the tag.
|
|
448
|
+
Clears existing contents and sets new string value.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
value (str): New string content
|
|
452
|
+
"""
|
|
453
|
+
self.clear()
|
|
454
|
+
if value is not None:
|
|
455
|
+
self.append(value)
|
|
456
|
+
|
|
415
457
|
def append(self, new_child: Union['Tag', NavigableString, str]) -> None:
|
|
416
|
-
"""Append a new child to this tag."""
|
|
458
|
+
"""Append a new child to this tag with error handling."""
|
|
417
459
|
if isinstance(new_child, str):
|
|
418
460
|
new_child = NavigableString(new_child)
|
|
419
|
-
new_child
|
|
461
|
+
if hasattr(new_child, 'parent'):
|
|
462
|
+
new_child.parent = self
|
|
420
463
|
self.contents.append(new_child)
|
|
421
464
|
|
|
422
465
|
def insert(self, index: int, new_child: Union['Tag', NavigableString, str]) -> None:
|
|
423
|
-
"""Insert a new child at the given index."""
|
|
466
|
+
"""Insert a new child at the given index with error handling."""
|
|
424
467
|
if isinstance(new_child, str):
|
|
425
468
|
new_child = NavigableString(new_child)
|
|
426
|
-
new_child
|
|
469
|
+
if hasattr(new_child, 'parent'):
|
|
470
|
+
new_child.parent = self
|
|
427
471
|
self.contents.insert(index, new_child)
|
|
428
472
|
|
|
429
473
|
def replace_with(self, new_tag: 'Tag') -> None:
|
|
430
|
-
"""
|
|
431
|
-
|
|
474
|
+
"""Replace this tag with another tag with error handling."""
|
|
475
|
+
if self.parent:
|
|
476
|
+
try:
|
|
477
|
+
index = self.parent.contents.index(self)
|
|
478
|
+
self.parent.contents[index] = new_tag
|
|
479
|
+
new_tag.parent = self.parent
|
|
480
|
+
except ValueError:
|
|
481
|
+
pass
|
|
482
|
+
|
|
483
|
+
def wrap(self, wrapper_tag: 'Tag') -> 'Tag':
|
|
484
|
+
"""Wrap this tag in another tag."""
|
|
485
|
+
if self.parent:
|
|
486
|
+
idx = self.parent.contents.index(self)
|
|
487
|
+
self.parent.contents[idx] = wrapper_tag
|
|
488
|
+
wrapper_tag.parent = self.parent
|
|
489
|
+
else:
|
|
490
|
+
wrapper_tag.parent = None
|
|
491
|
+
wrapper_tag.contents.append(self)
|
|
492
|
+
self.parent = wrapper_tag
|
|
493
|
+
return wrapper_tag
|
|
494
|
+
|
|
495
|
+
def unwrap(self) -> None:
|
|
496
|
+
"""Remove this tag but keep its contents in the parent."""
|
|
497
|
+
if self.parent:
|
|
498
|
+
idx = self.parent.contents.index(self)
|
|
499
|
+
for child in reversed(self.contents):
|
|
500
|
+
child.parent = self.parent
|
|
501
|
+
self.parent.contents.insert(idx, child)
|
|
502
|
+
self.parent.contents.remove(self)
|
|
503
|
+
self.parent = None
|
|
504
|
+
self.contents = []
|
|
432
505
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
506
|
+
def insert_before(self, new_element: 'Tag') -> None:
|
|
507
|
+
"""Insert a tag or string immediately before this tag."""
|
|
508
|
+
if self.parent:
|
|
509
|
+
idx = self.parent.contents.index(self)
|
|
510
|
+
new_element.parent = self.parent
|
|
511
|
+
self.parent.contents.insert(idx, new_element)
|
|
512
|
+
|
|
513
|
+
def insert_after(self, new_element: 'Tag') -> None:
|
|
514
|
+
"""Insert a tag or string immediately after this tag."""
|
|
436
515
|
if self.parent:
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
516
|
+
idx = self.parent.contents.index(self)
|
|
517
|
+
new_element.parent = self.parent
|
|
518
|
+
self.parent.contents.insert(idx + 1, new_element)
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def descendants(self):
|
|
522
|
+
"""Yield all descendants in document order."""
|
|
523
|
+
for child in self.contents:
|
|
524
|
+
yield child
|
|
525
|
+
if isinstance(child, Tag):
|
|
526
|
+
yield from child.descendants
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def parents(self):
|
|
530
|
+
"""Yield all parents up the tree."""
|
|
531
|
+
current = self.parent
|
|
532
|
+
while current:
|
|
533
|
+
yield current
|
|
534
|
+
current = current.parent
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def next_element(self):
|
|
538
|
+
"""Return the next element in document order."""
|
|
539
|
+
if self.contents:
|
|
540
|
+
return self.contents[0]
|
|
541
|
+
current = self
|
|
542
|
+
while current.parent:
|
|
543
|
+
idx = current.parent.contents.index(current)
|
|
544
|
+
if idx + 1 < len(current.parent.contents):
|
|
545
|
+
return current.parent.contents[idx + 1]
|
|
546
|
+
current = current.parent
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def previous_element(self):
|
|
551
|
+
"""Return the previous element in document order."""
|
|
552
|
+
if not self.parent:
|
|
553
|
+
return None
|
|
554
|
+
idx = self.parent.contents.index(self)
|
|
555
|
+
if idx > 0:
|
|
556
|
+
prev = self.parent.contents[idx - 1]
|
|
557
|
+
while isinstance(prev, Tag) and prev.contents:
|
|
558
|
+
prev = prev.contents[-1]
|
|
559
|
+
return prev
|
|
560
|
+
return self.parent
|
|
440
561
|
|
|
441
562
|
def decode_contents(self, eventual_encoding='utf-8') -> str:
|
|
442
563
|
"""
|
webscout/swiftcli/core/cli.py
CHANGED
|
@@ -223,9 +223,20 @@ class CLI:
|
|
|
223
223
|
# Handle options
|
|
224
224
|
if hasattr(func, '_options'):
|
|
225
225
|
for opt in func._options:
|
|
226
|
-
name
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
# Use the longest parameter name (usually the --long-form) for the parameter name
|
|
227
|
+
param_names = [p.lstrip('-').replace('-', '_') for p in opt['param_decls']]
|
|
228
|
+
name = max(param_names, key=len) # Use the longest name
|
|
229
|
+
|
|
230
|
+
# Check all possible parameter names in parsed args
|
|
231
|
+
value = None
|
|
232
|
+
found = False
|
|
233
|
+
for param_name in param_names:
|
|
234
|
+
if param_name in parsed_args:
|
|
235
|
+
value = parsed_args[param_name]
|
|
236
|
+
found = True
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
if found:
|
|
229
240
|
if 'type' in opt:
|
|
230
241
|
value = convert_type(value, opt['type'], name)
|
|
231
242
|
if 'choices' in opt and opt['choices']:
|
|
@@ -5,16 +5,62 @@ from typing import Any, Callable, List, Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
from rich.table import Table
|
|
8
|
-
from rich.progress import (
|
|
9
|
-
Progress,
|
|
10
|
-
SpinnerColumn,
|
|
11
|
-
TextColumn,
|
|
12
|
-
BarColumn,
|
|
13
|
-
TaskProgressColumn,
|
|
14
|
-
TimeRemainingColumn
|
|
15
|
-
)
|
|
16
8
|
from rich.panel import Panel
|
|
17
9
|
|
|
10
|
+
# Handle different versions of rich
|
|
11
|
+
try:
|
|
12
|
+
from rich.progress import (
|
|
13
|
+
Progress,
|
|
14
|
+
SpinnerColumn,
|
|
15
|
+
TextColumn,
|
|
16
|
+
BarColumn,
|
|
17
|
+
TaskProgressColumn,
|
|
18
|
+
TimeRemainingColumn
|
|
19
|
+
)
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Fallback for older versions of rich
|
|
22
|
+
try:
|
|
23
|
+
from rich.progress import (
|
|
24
|
+
Progress,
|
|
25
|
+
SpinnerColumn,
|
|
26
|
+
TextColumn,
|
|
27
|
+
BarColumn,
|
|
28
|
+
TimeRemainingColumn
|
|
29
|
+
)
|
|
30
|
+
# Create a simple TaskProgressColumn replacement for older versions
|
|
31
|
+
class TaskProgressColumn:
|
|
32
|
+
def __init__(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def __call__(self, task):
|
|
36
|
+
return f"{task.percentage:.1f}%"
|
|
37
|
+
|
|
38
|
+
except ImportError:
|
|
39
|
+
# If rich is too old, create minimal fallbacks
|
|
40
|
+
class Progress:
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
pass
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
return self
|
|
45
|
+
def __exit__(self, *args):
|
|
46
|
+
pass
|
|
47
|
+
def add_task(self, description, total=None):
|
|
48
|
+
return 0
|
|
49
|
+
def update(self, task_id, **kwargs):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
class SpinnerColumn:
|
|
53
|
+
pass
|
|
54
|
+
class TextColumn:
|
|
55
|
+
def __init__(self, text):
|
|
56
|
+
pass
|
|
57
|
+
class BarColumn:
|
|
58
|
+
pass
|
|
59
|
+
class TaskProgressColumn:
|
|
60
|
+
pass
|
|
61
|
+
class TimeRemainingColumn:
|
|
62
|
+
pass
|
|
63
|
+
|
|
18
64
|
console = Console()
|
|
19
65
|
|
|
20
66
|
def table_output(
|
|
@@ -111,7 +157,11 @@ def progress(
|
|
|
111
157
|
if show_bar:
|
|
112
158
|
columns.append(BarColumn())
|
|
113
159
|
if show_percentage:
|
|
114
|
-
|
|
160
|
+
try:
|
|
161
|
+
columns.append(TaskProgressColumn())
|
|
162
|
+
except:
|
|
163
|
+
# Fallback for older rich versions
|
|
164
|
+
columns.append(TextColumn("[progress.percentage]{task.percentage:>3.0f}%"))
|
|
115
165
|
if show_time:
|
|
116
166
|
columns.append(TimeRemainingColumn())
|
|
117
167
|
|
webscout/update_checker.py
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
def get_package_version(package_name: str) -> str:
|
|
3
|
-
return get_distribution(package_name).version
|
|
4
|
-
"""
|
|
5
|
-
Webscout Update Checker
|
|
6
|
-
>>> from webscout import check_for_updates
|
|
7
|
-
>>> result = check_for_updates()
|
|
8
|
-
>>> print(result)
|
|
9
|
-
'New Webscout version available: 2.0.0 - Update with: pip install --upgrade webscout'
|
|
10
|
-
"""
|
|
11
|
-
|
|
1
|
+
import importlib.metadata
|
|
12
2
|
from typing import Optional, Dict, Any, Literal
|
|
13
|
-
|
|
14
3
|
import requests
|
|
15
4
|
from packaging import version
|
|
16
5
|
|
|
17
6
|
# Constants
|
|
18
7
|
PYPI_URL = "https://pypi.org/pypi/webscout/json"
|
|
8
|
+
YOUTUBE_URL = "https://youtube.com/@OEvortex"
|
|
19
9
|
|
|
20
10
|
# Create a session for HTTP requests
|
|
21
11
|
session = requests.Session()
|
|
@@ -23,6 +13,13 @@ session = requests.Session()
|
|
|
23
13
|
# Version comparison result type
|
|
24
14
|
VersionCompareResult = Literal[-1, 0, 1]
|
|
25
15
|
|
|
16
|
+
def get_package_version(package_name: str) -> Optional[str]:
|
|
17
|
+
"""Get the installed version of a package by name."""
|
|
18
|
+
try:
|
|
19
|
+
return importlib.metadata.version(package_name)
|
|
20
|
+
except importlib.metadata.PackageNotFoundError:
|
|
21
|
+
return None
|
|
22
|
+
|
|
26
23
|
def get_installed_version() -> Optional[str]:
|
|
27
24
|
"""Get the currently installed version of webscout.
|
|
28
25
|
|
|
@@ -34,10 +31,7 @@ def get_installed_version() -> Optional[str]:
|
|
|
34
31
|
>>> print(version)
|
|
35
32
|
'1.2.3'
|
|
36
33
|
"""
|
|
37
|
-
|
|
38
|
-
return get_package_version('webscout')
|
|
39
|
-
except PackageNotFoundError:
|
|
40
|
-
return None
|
|
34
|
+
return get_package_version('webscout')
|
|
41
35
|
|
|
42
36
|
def get_pypi_version() -> Optional[str]:
|
|
43
37
|
"""Get the latest version available on PyPI.
|
|
@@ -54,19 +48,15 @@ def get_pypi_version() -> Optional[str]:
|
|
|
54
48
|
response = session.get(PYPI_URL, timeout=10)
|
|
55
49
|
response.raise_for_status()
|
|
56
50
|
data: Dict[str, Any] = response.json()
|
|
57
|
-
return data
|
|
51
|
+
return data.get('info', {}).get('version')
|
|
58
52
|
except (requests.RequestException, KeyError, ValueError):
|
|
59
53
|
return None
|
|
60
54
|
|
|
61
55
|
def version_compare(v1: str, v2: str) -> VersionCompareResult:
|
|
62
56
|
"""Compare two version strings.
|
|
63
57
|
|
|
64
|
-
Args:
|
|
65
|
-
v1: First version string
|
|
66
|
-
v2: Second version string to compare with
|
|
67
|
-
|
|
68
58
|
Returns:
|
|
69
|
-
|
|
59
|
+
-1 if v1 < v2, 1 if v1 > v2, 0 if equal or invalid
|
|
70
60
|
|
|
71
61
|
Examples:
|
|
72
62
|
>>> version_compare('1.0.0', '2.0.0')
|
|
@@ -80,48 +70,40 @@ def version_compare(v1: str, v2: str) -> VersionCompareResult:
|
|
|
80
70
|
if version1 > version2:
|
|
81
71
|
return 1
|
|
82
72
|
return 0
|
|
83
|
-
except
|
|
73
|
+
except Exception:
|
|
84
74
|
return 0
|
|
85
75
|
|
|
86
|
-
def get_update_message(installed: str, latest: str) ->
|
|
87
|
-
"""Generate appropriate update message based on version comparison.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
installed: Currently installed version
|
|
91
|
-
latest: Latest available version
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Optional[str]: Update message if needed, None if already on latest version
|
|
95
|
-
|
|
96
|
-
Examples:
|
|
97
|
-
>>> get_update_message('1.0.0', '2.0.0')
|
|
98
|
-
'New Webscout version available: 2.0.0 - Update with: pip install --upgrade webscout'
|
|
99
|
-
"""
|
|
76
|
+
def get_update_message(installed: str, latest: str) -> str:
|
|
77
|
+
"""Generate appropriate update message based on version comparison."""
|
|
100
78
|
comparison_result = version_compare(installed, latest)
|
|
79
|
+
youtube_msg = f'\033[1;32mSubscribe to my YouTube Channel: {YOUTUBE_URL}\033[0m'
|
|
101
80
|
if comparison_result < 0:
|
|
102
|
-
|
|
81
|
+
# Bold red for update available
|
|
82
|
+
return (
|
|
83
|
+
f"\033[1;31mNew Webscout version available: {latest} "
|
|
84
|
+
f"- Update with: pip install --upgrade webscout\033[0m\n{youtube_msg}"
|
|
85
|
+
)
|
|
103
86
|
elif comparison_result > 0:
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
# Bold yellow for dev version
|
|
88
|
+
return (
|
|
89
|
+
f"\033[1;33mYou're running a development version ({installed}) "
|
|
90
|
+
f"ahead of latest release ({latest})\033[0m\n{youtube_msg}"
|
|
91
|
+
)
|
|
92
|
+
# Bold green for up-to-date
|
|
106
93
|
|
|
107
94
|
def check_for_updates() -> Optional[str]:
|
|
108
95
|
"""Check if a newer version of Webscout is available.
|
|
109
96
|
|
|
110
97
|
Returns:
|
|
111
98
|
Optional[str]: Update message if newer version exists, None otherwise
|
|
112
|
-
|
|
113
|
-
Examples:
|
|
114
|
-
>>> result = check_for_updates()
|
|
115
|
-
>>> print(result)
|
|
116
|
-
'New Webscout version available: 2.0.0 - Update with: pip install --upgrade webscout'
|
|
117
99
|
"""
|
|
118
100
|
installed_version = get_installed_version()
|
|
119
101
|
if not installed_version:
|
|
120
|
-
return
|
|
102
|
+
return "\033[1;31mWebscout is not installed.\033[0m"
|
|
121
103
|
|
|
122
104
|
latest_version = get_pypi_version()
|
|
123
105
|
if not latest_version:
|
|
124
|
-
return
|
|
106
|
+
return "\033[1;31mCould not retrieve latest version from PyPI.\033[0m"
|
|
125
107
|
|
|
126
108
|
return get_update_message(installed_version, latest_version)
|
|
127
109
|
|
|
@@ -131,6 +113,6 @@ if __name__ == "__main__":
|
|
|
131
113
|
if update_message:
|
|
132
114
|
print(update_message)
|
|
133
115
|
except KeyboardInterrupt:
|
|
134
|
-
print("
|
|
116
|
+
print("\nUpdate check canceled by user.")
|
|
135
117
|
except Exception as e:
|
|
136
|
-
print(f"
|
|
118
|
+
print(f"\033[1;31mUpdate check failed: {str(e)}\033[0m")
|
webscout/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "8.3"
|
|
1
|
+
__version__ = "8.3.2"
|
|
2
2
|
__prog__ = "webscout"
|
webscout/webscout_search.py
CHANGED
|
@@ -16,7 +16,7 @@ from types import TracebackType
|
|
|
16
16
|
from typing import Any, cast
|
|
17
17
|
import os
|
|
18
18
|
from typing import Literal, Iterator
|
|
19
|
-
|
|
19
|
+
from webscout.litagent import LitAgent
|
|
20
20
|
import curl_cffi.requests # type: ignore
|
|
21
21
|
|
|
22
22
|
try:
|
|
@@ -83,17 +83,9 @@ class WEBS:
|
|
|
83
83
|
self.proxy = proxies.get("http") or proxies.get("https") if isinstance(proxies, dict) else proxies
|
|
84
84
|
|
|
85
85
|
default_headers = {
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"DNT": "1",
|
|
90
|
-
"Connection": "keep-alive",
|
|
91
|
-
"Upgrade-Insecure-Requests": "1",
|
|
92
|
-
"Sec-Fetch-Dest": "document",
|
|
93
|
-
"Sec-Fetch-Mode": "navigate",
|
|
94
|
-
"Sec-Fetch-Site": "none",
|
|
95
|
-
"Sec-Fetch-User": "?1",
|
|
96
|
-
"Referer": "https://duckduckgo.com/",
|
|
86
|
+
**LitAgent().generate_fingerprint(),
|
|
87
|
+
"Origin": "https://duckduckgo.com",
|
|
88
|
+
"Referer": "https://yep.com/",
|
|
97
89
|
}
|
|
98
90
|
|
|
99
91
|
self.headers = headers if headers else {}
|
|
@@ -16,6 +16,8 @@ from lxml.etree import _Element
|
|
|
16
16
|
from lxml.html import HTMLParser as LHTMLParser
|
|
17
17
|
from lxml.html import document_fromstring
|
|
18
18
|
|
|
19
|
+
from webscout.litagent.agent import LitAgent
|
|
20
|
+
|
|
19
21
|
from .exceptions import ConversationLimitException, RatelimitE, TimeoutE, WebscoutE
|
|
20
22
|
from .utils import (
|
|
21
23
|
_expand_proxy_tb_alias,
|
|
@@ -74,16 +76,7 @@ class AsyncWEBS:
|
|
|
74
76
|
self.proxy = proxies.get("http") or proxies.get("https") if isinstance(proxies, dict) else proxies
|
|
75
77
|
|
|
76
78
|
default_headers = {
|
|
77
|
-
|
|
78
|
-
"Accept-Language": "en-US,en;q=0.5",
|
|
79
|
-
"Accept-Encoding": "gzip, deflate, br",
|
|
80
|
-
"DNT": "1",
|
|
81
|
-
"Connection": "keep-alive",
|
|
82
|
-
"Upgrade-Insecure-Requests": "1",
|
|
83
|
-
"Sec-Fetch-Dest": "document",
|
|
84
|
-
"Sec-Fetch-Mode": "navigate",
|
|
85
|
-
"Sec-Fetch-Site": "none",
|
|
86
|
-
"Sec-Fetch-User": "?1",
|
|
79
|
+
**LitAgent().generate_fingerprint(),
|
|
87
80
|
"Referer": "https://duckduckgo.com/",
|
|
88
81
|
}
|
|
89
82
|
|
webscout/yep_search.py
CHANGED
|
@@ -35,20 +35,11 @@ class YepSearch:
|
|
|
35
35
|
timeout=timeout # Set timeout directly in session
|
|
36
36
|
)
|
|
37
37
|
self.session.headers.update({
|
|
38
|
-
|
|
39
|
-
"Accept-Language": "en-US,en;q=0.9,en-IN;q=0.8",
|
|
40
|
-
"DNT": "1",
|
|
38
|
+
**LitAgent().generate_fingerprint(),
|
|
41
39
|
"Origin": "https://yep.com",
|
|
42
40
|
"Referer": "https://yep.com/",
|
|
43
|
-
# Sec-Ch-Ua headers are often handled by impersonate, but keeping them might be safer
|
|
44
|
-
"Sec-Ch-Ua": '"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"',
|
|
45
|
-
"Sec-Ch-Ua-Mobile": "?0",
|
|
46
|
-
"Sec-Ch-Ua-Platform": '"Windows"',
|
|
47
|
-
"Sec-Fetch-Dest": "empty",
|
|
48
|
-
"Sec-Fetch-Mode": "cors",
|
|
49
|
-
"Sec-Fetch-Site": "same-site",
|
|
50
|
-
"User-Agent": LitAgent().random() # Keep custom User-Agent or rely on impersonate
|
|
51
41
|
})
|
|
42
|
+
|
|
52
43
|
# Proxies and verify are handled by the Session constructor now
|
|
53
44
|
|
|
54
45
|
def _remove_html_tags(self, text: str) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webscout
|
|
3
|
-
Version: 8.3
|
|
3
|
+
Version: 8.3.2
|
|
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
|
|
@@ -71,6 +71,8 @@ Requires-Dist: uvicorn[standard]; extra == "api"
|
|
|
71
71
|
Requires-Dist: pydantic; extra == "api"
|
|
72
72
|
Requires-Dist: python-multipart; extra == "api"
|
|
73
73
|
Requires-Dist: tiktoken; extra == "api"
|
|
74
|
+
Requires-Dist: motor; extra == "api"
|
|
75
|
+
Requires-Dist: jinja2; extra == "api"
|
|
74
76
|
Dynamic: license-file
|
|
75
77
|
|
|
76
78
|
<div align="center">
|
|
@@ -227,14 +229,14 @@ uv add "webscout[api]"
|
|
|
227
229
|
uv run webscout --help
|
|
228
230
|
|
|
229
231
|
# Run with API dependencies
|
|
230
|
-
uv run --extra api webscout-server
|
|
232
|
+
uv run webscout --extra api webscout-server
|
|
231
233
|
|
|
232
234
|
# Install as a UV tool for global access
|
|
233
235
|
uv tool install webscout
|
|
234
236
|
|
|
235
237
|
# Use UV tool commands
|
|
236
238
|
webscout --help
|
|
237
|
-
webscout-server
|
|
239
|
+
webscout-server
|
|
238
240
|
```
|
|
239
241
|
|
|
240
242
|
### 🔧 Development Installation
|
|
@@ -308,7 +310,7 @@ uv run --extra api webscout-server
|
|
|
308
310
|
```bash
|
|
309
311
|
# Traditional Python module execution
|
|
310
312
|
python -m webscout --help
|
|
311
|
-
python -m webscout
|
|
313
|
+
python -m webscout-server
|
|
312
314
|
```
|
|
313
315
|
|
|
314
316
|
<details open>
|
|
@@ -403,13 +405,19 @@ webscout-server --port 8080
|
|
|
403
405
|
# Start with API key authentication
|
|
404
406
|
webscout-server --api-key "your-secret-key"
|
|
405
407
|
|
|
408
|
+
# Start in no-auth mode using command line flag (no API key required)
|
|
409
|
+
webscout-server --no-auth
|
|
410
|
+
|
|
411
|
+
# Start in no-auth mode using environment variable
|
|
412
|
+
$env:WEBSCOUT_NO_AUTH='true'; webscout-server
|
|
413
|
+
|
|
406
414
|
# Specify a default provider
|
|
407
415
|
webscout-server --default-provider "Claude"
|
|
408
416
|
|
|
409
417
|
# Run in debug mode
|
|
410
418
|
webscout-server --debug
|
|
411
419
|
|
|
412
|
-
# Get help for all options
|
|
420
|
+
# Get help for all options (includes authentication options)
|
|
413
421
|
webscout-server --help
|
|
414
422
|
```
|
|
415
423
|
|
|
@@ -420,17 +428,36 @@ webscout-server --help
|
|
|
420
428
|
uv run --extra api webscout-server
|
|
421
429
|
|
|
422
430
|
# Using Python module
|
|
423
|
-
python -m webscout.
|
|
431
|
+
python -m webscout.auth.server
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Environment Variables
|
|
435
|
+
|
|
436
|
+
Webscout server supports configuration through environment variables:
|
|
424
437
|
|
|
425
|
-
|
|
426
|
-
|
|
438
|
+
```bash
|
|
439
|
+
# Start server in no-auth mode (no API key required)
|
|
440
|
+
$env:WEBSCOUT_NO_AUTH='true'; webscout-server
|
|
441
|
+
|
|
442
|
+
# Disable rate limiting
|
|
443
|
+
$env:WEBSCOUT_NO_RATE_LIMIT='true'; webscout-server
|
|
444
|
+
|
|
445
|
+
# Start with custom port using environment variable
|
|
446
|
+
$env:WEBSCOUT_PORT='7860'; webscout-server
|
|
427
447
|
```
|
|
428
448
|
|
|
449
|
+
For a complete list of supported environment variables and Docker deployment options, see [DOCKER.md](DOCKER.md).
|
|
450
|
+
|
|
451
|
+
|
|
429
452
|
#### From Python Code
|
|
430
453
|
|
|
454
|
+
> **Recommended:**
|
|
455
|
+
> Use `start_server` from `webscout.client` for the simplest programmatic startup.
|
|
456
|
+
> For advanced control (custom host, debug, etc.), use `run_api`.
|
|
457
|
+
|
|
431
458
|
```python
|
|
432
|
-
# Method 1: Using the helper function
|
|
433
|
-
from webscout.client import start_server
|
|
459
|
+
# Method 1: Using the helper function (recommended)
|
|
460
|
+
from webscout.client import start_server
|
|
434
461
|
|
|
435
462
|
# Start with default settings
|
|
436
463
|
start_server()
|
|
@@ -438,7 +465,10 @@ start_server()
|
|
|
438
465
|
# Start with custom settings
|
|
439
466
|
start_server(port=8080, api_key="your-secret-key", default_provider="Claude")
|
|
440
467
|
|
|
441
|
-
#
|
|
468
|
+
# Start in no-auth mode (no API key required)
|
|
469
|
+
start_server(no_auth=True)
|
|
470
|
+
|
|
471
|
+
# Method 2: Advanced usage with run_api
|
|
442
472
|
from webscout.client import run_api
|
|
443
473
|
|
|
444
474
|
run_api(
|