webscout 4.3__py3-none-any.whl → 4.5__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 +5 -7
- webscout/AIutel.py +2 -1
- webscout/Agents/Onlinesearcher.py +175 -0
- webscout/Agents/__init__.py +2 -0
- webscout/Agents/functioncall.py +126 -0
- webscout/Extra/autollama.py +20 -9
- webscout/Extra/gguf.py +5 -19
- webscout/Extra/weather.py +27 -9
- webscout/Extra/weather_ascii.py +5 -0
- webscout/GoogleS.py +342 -0
- webscout/Provider/Andi.py +275 -0
- webscout/Provider/__init__.py +3 -1
- webscout/__init__.py +8 -4
- webscout/cli.py +22 -21
- webscout/utils.py +13 -3
- webscout/voice.py +8 -1
- webscout/webai.py +16 -0
- webscout/webscout_search.py +35 -26
- webscout/websx_search.py +18 -369
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/METADATA +17 -25
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/RECORD +25 -20
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/LICENSE.md +0 -0
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/WHEEL +0 -0
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/entry_points.txt +0 -0
- {webscout-4.3.dist-info → webscout-4.5.dist-info}/top_level.txt +0 -0
webscout/cli.py
CHANGED
|
@@ -20,8 +20,10 @@ from rich.table import Table
|
|
|
20
20
|
from rich.style import Style
|
|
21
21
|
from rich.text import Text
|
|
22
22
|
from rich.align import Align
|
|
23
|
-
from rich.progress import track
|
|
23
|
+
from rich.progress import track, Progress
|
|
24
24
|
from rich.prompt import Prompt, Confirm
|
|
25
|
+
from rich.columns import Columns
|
|
26
|
+
from pyfiglet import figlet_format
|
|
25
27
|
|
|
26
28
|
logger = logging.getLogger(__name__)
|
|
27
29
|
|
|
@@ -45,13 +47,12 @@ COLORS = {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
def _print_data(data):
|
|
48
|
-
"""Prints data using rich panels and markdown
|
|
50
|
+
"""Prints data using rich panels and markdown."""
|
|
49
51
|
console = Console()
|
|
50
52
|
if data:
|
|
51
53
|
for i, e in enumerate(data, start=1):
|
|
52
|
-
|
|
53
|
-
table =
|
|
54
|
-
table.add_column("Key", style="cyan", no_wrap=True)
|
|
54
|
+
table = Table(show_header=False, show_lines=True, expand=True, box=None) # Removed duplicate title
|
|
55
|
+
table.add_column("Key", style="cyan", no_wrap=True, width=15)
|
|
55
56
|
table.add_column("Value", style="white")
|
|
56
57
|
|
|
57
58
|
for j, (k, v) in enumerate(e.items(), start=1):
|
|
@@ -59,21 +60,22 @@ def _print_data(data):
|
|
|
59
60
|
width = 300 if k in ("content", "href", "image", "source", "thumbnail", "url") else 78
|
|
60
61
|
k = "language" if k == "detected_language" else k
|
|
61
62
|
text = click.wrap_text(
|
|
62
|
-
f"{v}", width=width, initial_indent="", subsequent_indent=" " *
|
|
63
|
-
)
|
|
63
|
+
f"{v}", width=width, initial_indent="", subsequent_indent=" " * 18, preserve_paragraphs=True
|
|
64
|
+
).replace("\n", "\n\n")
|
|
64
65
|
else:
|
|
65
66
|
text = v
|
|
66
67
|
table.add_row(k, text)
|
|
67
68
|
|
|
68
|
-
#
|
|
69
|
-
console.print(Panel(
|
|
70
|
-
console.print("\n")
|
|
69
|
+
# Only the Panel has the title now
|
|
70
|
+
console.print(Panel(table, title=f"Result {i}", expand=False, style="green on black"))
|
|
71
|
+
console.print("\n")
|
|
72
|
+
|
|
71
73
|
|
|
72
74
|
def _sanitize_keywords(keywords):
|
|
73
75
|
"""Sanitizes keywords for file names and paths. Removes invalid characters like ':'. """
|
|
74
76
|
keywords = (
|
|
75
77
|
keywords.replace("filetype", "")
|
|
76
|
-
.replace(":", "")
|
|
78
|
+
.replace(":", "")
|
|
77
79
|
.replace('"', "'")
|
|
78
80
|
.replace("site", "")
|
|
79
81
|
.replace(" ", "_")
|
|
@@ -86,8 +88,8 @@ def _sanitize_keywords(keywords):
|
|
|
86
88
|
@click.group(chain=True)
|
|
87
89
|
def cli():
|
|
88
90
|
"""webscout CLI tool - Search the web with a rich UI."""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
console = Console()
|
|
92
|
+
console.print(f"[bold blue]{figlet_format('Webscout')}[/]\n", justify="center")
|
|
91
93
|
|
|
92
94
|
def safe_entry_point():
|
|
93
95
|
try:
|
|
@@ -100,7 +102,7 @@ def safe_entry_point():
|
|
|
100
102
|
def version():
|
|
101
103
|
"""Shows the current version of webscout."""
|
|
102
104
|
console = Console()
|
|
103
|
-
console.print(Panel(Text(f"webscout v{__version__}", style="cyan"), title="Version"))
|
|
105
|
+
console.print(Panel(Text(f"webscout v{__version__}", style="cyan"), title="Version", expand=False))
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
@cli.command()
|
|
@@ -111,16 +113,15 @@ def chat(proxy):
|
|
|
111
113
|
client = WEBS(proxy=proxy)
|
|
112
114
|
|
|
113
115
|
console = Console()
|
|
114
|
-
console.print(Panel(Text("Available AI Models:", style="cyan"), title="DuckDuckGo AI Chat"))
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
chosen_model_idx = Prompt.ask("Choose a model by entering its number [1]", choices=[str(i) for i in range(1, len(models) + 1)], default="1")
|
|
116
|
+
console.print(Panel(Text("Available AI Models:", style="cyan"), title="DuckDuckGo AI Chat", expand=False))
|
|
117
|
+
console.print(Columns([Panel(Text(model, justify="center"), expand=True) for model in models]))
|
|
118
|
+
chosen_model_idx = Prompt.ask("[bold cyan]Choose a model by entering its number[/] [1]", choices=[str(i) for i in range(1, len(models) + 1)], default="1")
|
|
118
119
|
chosen_model_idx = int(chosen_model_idx) - 1
|
|
119
120
|
model = models[chosen_model_idx]
|
|
120
|
-
console.print(f"Using model: {model}")
|
|
121
|
+
console.print(f"[bold green]Using model:[/] {model}")
|
|
121
122
|
|
|
122
123
|
while True:
|
|
123
|
-
user_input =
|
|
124
|
+
user_input = Prompt.ask(f"{'-'*78}\n[bold blue]You:[/]")
|
|
124
125
|
if not user_input.strip():
|
|
125
126
|
break
|
|
126
127
|
|
|
@@ -129,7 +130,7 @@ def chat(proxy):
|
|
|
129
130
|
console.print(Panel(Text(f"AI: {text}", style="green"), title="AI Response"))
|
|
130
131
|
|
|
131
132
|
if "exit" in user_input.lower() or "quit" in user_input.lower():
|
|
132
|
-
console.print(Panel(Text("Exiting chat session.", style="cyan"), title="Goodbye"))
|
|
133
|
+
console.print(Panel(Text("Exiting chat session.", style="cyan"), title="Goodbye", expand=False))
|
|
133
134
|
break
|
|
134
135
|
|
|
135
136
|
|
webscout/utils.py
CHANGED
|
@@ -4,23 +4,33 @@ from html import unescape
|
|
|
4
4
|
from math import atan2, cos, radians, sin, sqrt
|
|
5
5
|
from typing import Any, Dict, List, Union
|
|
6
6
|
from urllib.parse import unquote
|
|
7
|
-
import orjson
|
|
8
7
|
|
|
9
8
|
from .exceptions import WebscoutE
|
|
10
9
|
|
|
10
|
+
try:
|
|
11
|
+
HAS_ORJSON = True
|
|
12
|
+
import orjson
|
|
13
|
+
except ImportError:
|
|
14
|
+
HAS_ORJSON = False
|
|
15
|
+
import json
|
|
16
|
+
|
|
11
17
|
REGEX_STRIP_TAGS = re.compile("<.*?>")
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
def json_dumps(obj: Any) -> str:
|
|
15
21
|
try:
|
|
16
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
orjson.dumps(obj, option=orjson.OPT_INDENT_2).decode()
|
|
24
|
+
if HAS_ORJSON
|
|
25
|
+
else json.dumps(obj, ensure_ascii=False, indent=2)
|
|
26
|
+
)
|
|
17
27
|
except Exception as ex:
|
|
18
28
|
raise WebscoutE(f"{type(ex).__name__}: {ex}") from ex
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
def json_loads(obj: Union[str, bytes]) -> Any:
|
|
22
32
|
try:
|
|
23
|
-
return orjson.loads(obj)
|
|
33
|
+
return orjson.loads(obj) if HAS_ORJSON else json.loads(obj)
|
|
24
34
|
except Exception as ex:
|
|
25
35
|
raise WebscoutE(f"{type(ex).__name__}: {ex}") from ex
|
|
26
36
|
|
webscout/voice.py
CHANGED
|
@@ -24,4 +24,11 @@ def play_audio(message: str, voice: str = "Brian") -> typing.Union[str, typing.N
|
|
|
24
24
|
result = requests.get(url=url, headers=headers)
|
|
25
25
|
return result.content
|
|
26
26
|
except:
|
|
27
|
-
return None
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
# Example usage of the play_audio function
|
|
31
|
+
message = "Hello, world!"
|
|
32
|
+
voice = "Brian"
|
|
33
|
+
audio_result = play_audio(message, voice)
|
|
34
|
+
print(audio_result)
|
webscout/webai.py
CHANGED
|
@@ -37,11 +37,13 @@ from dotenv import load_dotenv
|
|
|
37
37
|
import g4f
|
|
38
38
|
import webscout
|
|
39
39
|
import webscout.AIutel
|
|
40
|
+
from pyfiglet import figlet_format
|
|
40
41
|
|
|
41
42
|
init_colorama(autoreset=True)
|
|
42
43
|
|
|
43
44
|
load_dotenv() # loads .env variables
|
|
44
45
|
|
|
46
|
+
console = Console()
|
|
45
47
|
logging.basicConfig(
|
|
46
48
|
format="%(asctime)s - %(levelname)s : %(message)s ",
|
|
47
49
|
datefmt="%H:%M:%S",
|
|
@@ -763,7 +765,20 @@ class Main(cmd.Cmd):
|
|
|
763
765
|
model=getOr(model, "Phind Model"),
|
|
764
766
|
quiet=quiet,
|
|
765
767
|
)
|
|
768
|
+
elif provider == "andi":
|
|
769
|
+
from webscout import AndiSearch
|
|
766
770
|
|
|
771
|
+
self.bot = AndiSearch(
|
|
772
|
+
is_conversation=disable_conversation,
|
|
773
|
+
max_tokens=max_tokens,
|
|
774
|
+
timeout=timeout,
|
|
775
|
+
intro=intro,
|
|
776
|
+
filepath=filepath,
|
|
777
|
+
update_file=update_file,
|
|
778
|
+
proxies=proxies,
|
|
779
|
+
history_offset=history_offset,
|
|
780
|
+
act=awesome_prompt,
|
|
781
|
+
)
|
|
767
782
|
elif provider == "blackboxai":
|
|
768
783
|
|
|
769
784
|
from webscout import BLACKBOXAI
|
|
@@ -2606,6 +2621,7 @@ def make_commands():
|
|
|
2606
2621
|
# @this.handle_exception
|
|
2607
2622
|
def main(*args):
|
|
2608
2623
|
"""Fireup console programmically"""
|
|
2624
|
+
console.print(f"[bold green]{figlet_format('WebAI')}[/]\n", justify="center")
|
|
2609
2625
|
sys.argv += list(args)
|
|
2610
2626
|
args = sys.argv
|
|
2611
2627
|
if len(args) == 1:
|
webscout/webscout_search.py
CHANGED
|
@@ -5,11 +5,14 @@ from datetime import datetime, timezone
|
|
|
5
5
|
from decimal import Decimal
|
|
6
6
|
from functools import cached_property
|
|
7
7
|
from itertools import cycle, islice
|
|
8
|
+
from random import choice
|
|
8
9
|
from threading import Event
|
|
9
10
|
from types import TracebackType
|
|
10
11
|
from typing import Dict, List, Optional, Tuple, Type, Union, cast
|
|
11
12
|
|
|
12
|
-
import pyreqwest_impersonate as pri
|
|
13
|
+
import pyreqwest_impersonate as pri
|
|
14
|
+
|
|
15
|
+
from .utils import _calculate_distance, _extract_vqd, _normalize, _normalize_url, _text_extract_json, json_loads # type: ignore
|
|
13
16
|
|
|
14
17
|
try:
|
|
15
18
|
from lxml.etree import _Element
|
|
@@ -20,15 +23,8 @@ try:
|
|
|
20
23
|
except ImportError:
|
|
21
24
|
LXML_AVAILABLE = False
|
|
22
25
|
|
|
23
|
-
from .exceptions import
|
|
24
|
-
|
|
25
|
-
_calculate_distance,
|
|
26
|
-
_extract_vqd,
|
|
27
|
-
_normalize,
|
|
28
|
-
_normalize_url,
|
|
29
|
-
_text_extract_json,
|
|
30
|
-
json_loads,
|
|
31
|
-
)
|
|
26
|
+
from .exceptions import *
|
|
27
|
+
|
|
32
28
|
|
|
33
29
|
logger = logging.getLogger("webscout.WEBS")
|
|
34
30
|
|
|
@@ -37,6 +33,15 @@ class WEBS:
|
|
|
37
33
|
"""webscout class to get search results from duckduckgo.com."""
|
|
38
34
|
|
|
39
35
|
_executor: ThreadPoolExecutor = ThreadPoolExecutor()
|
|
36
|
+
_impersonates = (
|
|
37
|
+
"chrome_99", "chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106", "chrome_108",
|
|
38
|
+
"chrome_107", "chrome_109", "chrome_114", "chrome_116", "chrome_117", "chrome_118", "chrome_119",
|
|
39
|
+
"chrome_120", #"chrome_123", "chrome_124", "chrome_126",
|
|
40
|
+
"safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", "safari_15.3", "safari_15.5",
|
|
41
|
+
"safari_15.6.1", "safari_16", "safari_16.5", "safari_17.2.1", "safari_17.4.1", "safari_17.5",
|
|
42
|
+
#"okhttp_3.9", "okhttp_3.11", "okhttp_3.13", "okhttp_3.14", "okhttp_4.9", "okhttp_4.10", "okhttp_5",
|
|
43
|
+
"edge_99", "edge_101", "edge_122",
|
|
44
|
+
) # fmt: skip
|
|
40
45
|
|
|
41
46
|
def __init__(
|
|
42
47
|
self,
|
|
@@ -66,7 +71,7 @@ class WEBS:
|
|
|
66
71
|
timeout=timeout,
|
|
67
72
|
cookie_store=True,
|
|
68
73
|
referer=True,
|
|
69
|
-
impersonate=
|
|
74
|
+
impersonate=choice(self._impersonates),
|
|
70
75
|
follow_redirects=False,
|
|
71
76
|
verify=False,
|
|
72
77
|
)
|
|
@@ -120,13 +125,14 @@ class WEBS:
|
|
|
120
125
|
resp_content = self._get_url("POST", "https://duckduckgo.com", data={"q": keywords})
|
|
121
126
|
return _extract_vqd(resp_content, keywords)
|
|
122
127
|
|
|
123
|
-
def chat(self, keywords: str, model: str = "gpt-3.5") -> str:
|
|
128
|
+
def chat(self, keywords: str, model: str = "gpt-3.5", timeout: int = 20) -> str:
|
|
124
129
|
"""Initiates a chat session with DuckDuckGo AI.
|
|
125
130
|
|
|
126
131
|
Args:
|
|
127
132
|
keywords (str): The initial message or question to send to the AI.
|
|
128
133
|
model (str): The model to use: "gpt-3.5", "claude-3-haiku", "llama-3-70b", "mixtral-8x7b".
|
|
129
134
|
Defaults to "gpt-3.5".
|
|
135
|
+
timeout (int): Timeout value for the HTTP client. Defaults to 20.
|
|
130
136
|
|
|
131
137
|
Returns:
|
|
132
138
|
str: The response from the AI.
|
|
@@ -136,6 +142,7 @@ class WEBS:
|
|
|
136
142
|
"gpt-3.5": "gpt-3.5-turbo-0125",
|
|
137
143
|
"llama-3-70b": "meta-llama/Llama-3-70b-chat-hf",
|
|
138
144
|
"mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
|
145
|
+
"gpt-4o-mini": "gpt-4o-mini",
|
|
139
146
|
}
|
|
140
147
|
# vqd
|
|
141
148
|
if not self._chat_vqd:
|
|
@@ -149,18 +156,16 @@ class WEBS:
|
|
|
149
156
|
"messages": self._chat_messages,
|
|
150
157
|
}
|
|
151
158
|
resp = self.client.post(
|
|
152
|
-
"https://duckduckgo.com/duckchat/v1/chat",
|
|
159
|
+
"https://duckduckgo.com/duckchat/v1/chat",
|
|
160
|
+
headers={"x-vqd-4": self._chat_vqd},
|
|
161
|
+
json=json_data,
|
|
162
|
+
timeout=timeout,
|
|
153
163
|
)
|
|
154
164
|
self._chat_vqd = resp.headers.get("x-vqd-4", "")
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if x:
|
|
160
|
-
j = json_loads(x)
|
|
161
|
-
message = j.get("message", "")
|
|
162
|
-
messages.append(message)
|
|
163
|
-
result = "".join(messages)
|
|
166
|
+
data = ",".join(x for line in resp.text.rstrip("[DONE]\n").split("data:") if (x := line.strip()))
|
|
167
|
+
result = "".join(x.get("message", "") for x in json_loads("[" + data + "]"))
|
|
168
|
+
|
|
164
169
|
self._chat_messages.append({"role": "assistant", "content": result})
|
|
165
170
|
return result
|
|
166
171
|
|
|
@@ -347,7 +352,7 @@ class WEBS:
|
|
|
347
352
|
for e in elements:
|
|
348
353
|
if isinstance(e, _Element):
|
|
349
354
|
hrefxpath = e.xpath("./a/@href")
|
|
350
|
-
href = str(hrefxpath[0]) if isinstance(hrefxpath, List) else None
|
|
355
|
+
href = str(hrefxpath[0]) if hrefxpath and isinstance(hrefxpath, List) else None
|
|
351
356
|
if (
|
|
352
357
|
href
|
|
353
358
|
and href not in cache
|
|
@@ -357,9 +362,9 @@ class WEBS:
|
|
|
357
362
|
):
|
|
358
363
|
cache.add(href)
|
|
359
364
|
titlexpath = e.xpath("./h2/a/text()")
|
|
360
|
-
title = str(titlexpath[0]) if isinstance(titlexpath, List) else ""
|
|
365
|
+
title = str(titlexpath[0]) if titlexpath and isinstance(titlexpath, List) else ""
|
|
361
366
|
bodyxpath = e.xpath("./a//text()")
|
|
362
|
-
body = "".join(str(x) for x in bodyxpath) if isinstance(bodyxpath, List) else ""
|
|
367
|
+
body = "".join(str(x) for x in bodyxpath) if bodyxpath and isinstance(bodyxpath, List) else ""
|
|
363
368
|
result = {
|
|
364
369
|
"title": _normalize(title),
|
|
365
370
|
"href": _normalize_url(href),
|
|
@@ -449,10 +454,14 @@ class WEBS:
|
|
|
449
454
|
else:
|
|
450
455
|
cache.add(href)
|
|
451
456
|
titlexpath = e.xpath(".//a//text()")
|
|
452
|
-
title = str(titlexpath[0]) if isinstance(titlexpath, List) else ""
|
|
457
|
+
title = str(titlexpath[0]) if titlexpath and isinstance(titlexpath, List) else ""
|
|
453
458
|
elif i == 2:
|
|
454
459
|
bodyxpath = e.xpath(".//td[@class='result-snippet']//text()")
|
|
455
|
-
body =
|
|
460
|
+
body = (
|
|
461
|
+
"".join(str(x) for x in bodyxpath).strip()
|
|
462
|
+
if bodyxpath and isinstance(bodyxpath, List)
|
|
463
|
+
else ""
|
|
464
|
+
)
|
|
456
465
|
if href:
|
|
457
466
|
result = {
|
|
458
467
|
"title": _normalize(title),
|