webscout 7.7__py3-none-any.whl → 7.9__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/AIutel.py +2 -1
- webscout/Bard.py +12 -29
- webscout/DWEBS.py +477 -461
- webscout/Extra/__init__.py +2 -0
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/{rawdog.py → autocoder.py} +849 -790
- webscout/Extra/autocoder/autocoder_utiles.py +332 -194
- webscout/Extra/gguf.py +682 -682
- webscout/Extra/tempmail/__init__.py +26 -0
- webscout/Extra/tempmail/async_utils.py +141 -0
- webscout/Extra/tempmail/base.py +156 -0
- webscout/Extra/tempmail/cli.py +187 -0
- webscout/Extra/tempmail/mail_tm.py +361 -0
- webscout/Extra/tempmail/temp_mail_io.py +292 -0
- webscout/Provider/AI21.py +1 -1
- webscout/Provider/AISEARCH/DeepFind.py +2 -2
- webscout/Provider/AISEARCH/ISou.py +2 -2
- webscout/Provider/AISEARCH/felo_search.py +6 -6
- webscout/Provider/AISEARCH/genspark_search.py +1 -1
- webscout/Provider/Aitopia.py +292 -0
- webscout/Provider/AllenAI.py +1 -1
- webscout/Provider/Andi.py +3 -3
- webscout/Provider/C4ai.py +1 -1
- webscout/Provider/ChatGPTES.py +3 -5
- webscout/Provider/ChatGPTGratis.py +4 -4
- webscout/Provider/Chatify.py +2 -2
- webscout/Provider/Cloudflare.py +3 -2
- webscout/Provider/DeepSeek.py +2 -2
- webscout/Provider/Deepinfra.py +288 -286
- webscout/Provider/ElectronHub.py +709 -634
- webscout/Provider/ExaChat.py +325 -0
- webscout/Provider/Free2GPT.py +2 -2
- webscout/Provider/Gemini.py +167 -179
- webscout/Provider/GithubChat.py +1 -1
- webscout/Provider/Glider.py +4 -4
- webscout/Provider/Groq.py +41 -27
- webscout/Provider/HF_space/qwen_qwen2.py +1 -1
- webscout/Provider/HeckAI.py +1 -1
- webscout/Provider/HuggingFaceChat.py +1 -1
- webscout/Provider/Hunyuan.py +1 -1
- webscout/Provider/Jadve.py +3 -3
- webscout/Provider/Koboldai.py +3 -3
- webscout/Provider/LambdaChat.py +3 -2
- webscout/Provider/Llama.py +3 -5
- webscout/Provider/Llama3.py +4 -12
- webscout/Provider/Marcus.py +3 -3
- webscout/Provider/OLLAMA.py +8 -8
- webscout/Provider/Openai.py +7 -3
- webscout/Provider/PI.py +1 -1
- webscout/Provider/Perplexitylabs.py +1 -1
- webscout/Provider/Phind.py +1 -1
- webscout/Provider/PizzaGPT.py +1 -1
- webscout/Provider/QwenLM.py +4 -7
- webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +3 -1
- webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +3 -3
- webscout/Provider/TTI/ImgSys/__init__.py +23 -0
- webscout/Provider/TTI/ImgSys/async_imgsys.py +202 -0
- webscout/Provider/TTI/ImgSys/sync_imgsys.py +195 -0
- webscout/Provider/TTI/__init__.py +3 -1
- webscout/Provider/TTI/artbit/async_artbit.py +1 -1
- webscout/Provider/TTI/artbit/sync_artbit.py +1 -1
- webscout/Provider/TTI/huggingface/async_huggingface.py +1 -1
- webscout/Provider/TTI/huggingface/sync_huggingface.py +1 -1
- webscout/Provider/TTI/piclumen/__init__.py +22 -22
- webscout/Provider/TTI/piclumen/sync_piclumen.py +232 -232
- webscout/Provider/TTI/pixelmuse/__init__.py +4 -0
- webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +249 -0
- webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +182 -0
- webscout/Provider/TTI/talkai/sync_talkai.py +1 -1
- webscout/Provider/TTS/utils.py +1 -1
- webscout/Provider/TeachAnything.py +1 -1
- webscout/Provider/TextPollinationsAI.py +232 -230
- webscout/Provider/TwoAI.py +1 -2
- webscout/Provider/Venice.py +4 -2
- webscout/Provider/VercelAI.py +234 -0
- webscout/Provider/WebSim.py +3 -2
- webscout/Provider/WiseCat.py +10 -12
- webscout/Provider/Youchat.py +1 -1
- webscout/Provider/__init__.py +10 -4
- webscout/Provider/ai4chat.py +1 -1
- webscout/Provider/aimathgpt.py +2 -6
- webscout/Provider/akashgpt.py +1 -1
- webscout/Provider/askmyai.py +4 -4
- webscout/Provider/{DARKAI.py → asksteve.py} +56 -77
- webscout/Provider/bagoodex.py +2 -2
- webscout/Provider/cerebras.py +1 -1
- webscout/Provider/chatglm.py +4 -4
- webscout/Provider/cleeai.py +1 -0
- webscout/Provider/copilot.py +21 -9
- webscout/Provider/elmo.py +1 -1
- webscout/Provider/flowith.py +1 -1
- webscout/Provider/freeaichat.py +64 -31
- webscout/Provider/gaurish.py +3 -5
- webscout/Provider/geminiprorealtime.py +1 -1
- webscout/Provider/granite.py +4 -4
- webscout/Provider/hermes.py +5 -5
- webscout/Provider/julius.py +1 -1
- webscout/Provider/koala.py +1 -1
- webscout/Provider/lepton.py +1 -1
- webscout/Provider/llama3mitril.py +4 -4
- webscout/Provider/llamatutor.py +1 -1
- webscout/Provider/llmchat.py +3 -3
- webscout/Provider/meta.py +1 -1
- webscout/Provider/multichat.py +10 -10
- webscout/Provider/promptrefine.py +1 -1
- webscout/Provider/searchchat.py +293 -0
- webscout/Provider/sonus.py +2 -2
- webscout/Provider/talkai.py +2 -2
- webscout/Provider/turboseek.py +1 -1
- webscout/Provider/tutorai.py +1 -1
- webscout/Provider/typegpt.py +5 -42
- webscout/Provider/uncovr.py +312 -297
- webscout/Provider/x0gpt.py +1 -1
- webscout/Provider/yep.py +64 -12
- webscout/__init__.py +3 -1
- webscout/cli.py +59 -98
- webscout/conversation.py +350 -17
- webscout/litprinter/__init__.py +59 -667
- webscout/optimizers.py +419 -419
- webscout/tempid.py +11 -11
- webscout/update_checker.py +14 -12
- webscout/utils.py +2 -2
- webscout/version.py +1 -1
- webscout/webscout_search.py +146 -87
- webscout/webscout_search_async.py +148 -27
- {webscout-7.7.dist-info → webscout-7.9.dist-info}/METADATA +92 -66
- webscout-7.9.dist-info/RECORD +248 -0
- webscout/Provider/EDITEE.py +0 -192
- webscout/litprinter/colors.py +0 -54
- webscout-7.7.dist-info/RECORD +0 -234
- {webscout-7.7.dist-info → webscout-7.9.dist-info}/LICENSE.md +0 -0
- {webscout-7.7.dist-info → webscout-7.9.dist-info}/WHEEL +0 -0
- {webscout-7.7.dist-info → webscout-7.9.dist-info}/entry_points.txt +0 -0
- {webscout-7.7.dist-info → webscout-7.9.dist-info}/top_level.txt +0 -0
webscout/tempid.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import aiohttp
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import NoReturn, List, Dict, Any
|
|
3
|
+
from typing import NoReturn, List, Dict, Any, Optional, Union
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
6
|
@dataclass
|
|
@@ -19,15 +19,15 @@ class CreateEmailResponseModel:
|
|
|
19
19
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class MessageResponseModel:
|
|
22
|
-
attachments:
|
|
23
|
-
body_html: str
|
|
24
|
-
body_text: str
|
|
25
|
-
cc: str
|
|
22
|
+
attachments: Optional[List[Any]]
|
|
23
|
+
body_html: Optional[str]
|
|
24
|
+
body_text: Optional[str]
|
|
25
|
+
cc: Optional[str]
|
|
26
26
|
created_at: str
|
|
27
|
-
email_from: str
|
|
27
|
+
email_from: Optional[str]
|
|
28
28
|
id: str
|
|
29
|
-
subject: str
|
|
30
|
-
email_to: str
|
|
29
|
+
subject: Optional[str]
|
|
30
|
+
email_to: Optional[str]
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class TempMail:
|
|
@@ -52,12 +52,12 @@ class TempMail:
|
|
|
52
52
|
await self.close()
|
|
53
53
|
return None
|
|
54
54
|
|
|
55
|
-
async def get_domains(self) ->
|
|
55
|
+
async def get_domains(self) -> List[DomainModel]:
|
|
56
56
|
async with self._session.get("/api/v3/domains") as response:
|
|
57
57
|
response_json = await response.json()
|
|
58
58
|
return [DomainModel(domain['name'], domain['type'], domain['forward_available'], domain['forward_max_seconds']) for domain in response_json['domains']]
|
|
59
59
|
|
|
60
|
-
async def create_email(self, alias: str
|
|
60
|
+
async def create_email(self, alias: Optional[str] = None, domain: Optional[str] = None) -> CreateEmailResponseModel:
|
|
61
61
|
async with self._session.post("/api/v3/email/new", data={'name': alias, 'domain': domain}) as response:
|
|
62
62
|
response_json = await response.json()
|
|
63
63
|
return CreateEmailResponseModel(response_json['email'], response_json['token'])
|
|
@@ -69,7 +69,7 @@ class TempMail:
|
|
|
69
69
|
else:
|
|
70
70
|
return False
|
|
71
71
|
|
|
72
|
-
async def get_messages(self, email: str) ->
|
|
72
|
+
async def get_messages(self, email: str) -> Optional[List[MessageResponseModel]]:
|
|
73
73
|
async with self._session.get(f"/api/v3/email/{email}/messages") as response:
|
|
74
74
|
response_json = await response.json()
|
|
75
75
|
if len(response_json) == 0:
|
webscout/update_checker.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from pkg_resources import get_distribution, DistributionNotFound as PackageNotFoundError
|
|
2
|
+
def get_package_version(package_name: str) -> str:
|
|
3
|
+
return get_distribution(package_name).version
|
|
1
4
|
"""
|
|
2
5
|
Webscout Update Checker
|
|
3
6
|
>>> from webscout import check_for_updates
|
|
@@ -10,8 +13,12 @@ from typing import Optional, Dict, Any, Literal
|
|
|
10
13
|
|
|
11
14
|
import requests
|
|
12
15
|
from packaging import version
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
# Constants
|
|
18
|
+
PYPI_URL = "https://pypi.org/pypi/webscout/json"
|
|
19
|
+
|
|
20
|
+
# Create a session for HTTP requests
|
|
21
|
+
session = requests.Session()
|
|
15
22
|
|
|
16
23
|
# Version comparison result type
|
|
17
24
|
VersionCompareResult = Literal[-1, 0, 1]
|
|
@@ -31,8 +38,6 @@ def get_installed_version() -> Optional[str]:
|
|
|
31
38
|
return get_package_version('webscout')
|
|
32
39
|
except PackageNotFoundError:
|
|
33
40
|
return None
|
|
34
|
-
except Exception:
|
|
35
|
-
return None
|
|
36
41
|
|
|
37
42
|
def get_pypi_version() -> Optional[str]:
|
|
38
43
|
"""Get the latest version available on PyPI.
|
|
@@ -46,14 +51,11 @@ def get_pypi_version() -> Optional[str]:
|
|
|
46
51
|
'2.0.0'
|
|
47
52
|
"""
|
|
48
53
|
try:
|
|
49
|
-
response =
|
|
50
|
-
"https://pypi.org/pypi/webscout/json",
|
|
51
|
-
timeout=10
|
|
52
|
-
)
|
|
54
|
+
response = session.get(PYPI_URL, timeout=10)
|
|
53
55
|
response.raise_for_status()
|
|
54
56
|
data: Dict[str, Any] = response.json()
|
|
55
57
|
return data['info']['version']
|
|
56
|
-
except (requests.RequestException, KeyError, ValueError
|
|
58
|
+
except (requests.RequestException, KeyError, ValueError):
|
|
57
59
|
return None
|
|
58
60
|
|
|
59
61
|
def version_compare(v1: str, v2: str) -> VersionCompareResult:
|
|
@@ -78,7 +80,7 @@ def version_compare(v1: str, v2: str) -> VersionCompareResult:
|
|
|
78
80
|
if version1 > version2:
|
|
79
81
|
return 1
|
|
80
82
|
return 0
|
|
81
|
-
except
|
|
83
|
+
except version.InvalidVersion:
|
|
82
84
|
return 0
|
|
83
85
|
|
|
84
86
|
def get_update_message(installed: str, latest: str) -> Optional[str]:
|
|
@@ -116,11 +118,11 @@ def check_for_updates() -> Optional[str]:
|
|
|
116
118
|
installed_version = get_installed_version()
|
|
117
119
|
if not installed_version:
|
|
118
120
|
return None
|
|
119
|
-
|
|
121
|
+
|
|
120
122
|
latest_version = get_pypi_version()
|
|
121
123
|
if not latest_version:
|
|
122
124
|
return None
|
|
123
|
-
|
|
125
|
+
|
|
124
126
|
return get_update_message(installed_version, latest_version)
|
|
125
127
|
|
|
126
128
|
if __name__ == "__main__":
|
webscout/utils.py
CHANGED
|
@@ -2,7 +2,7 @@ import re
|
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
from html import unescape
|
|
4
4
|
from math import atan2, cos, radians, sin, sqrt
|
|
5
|
-
from typing import Any, Dict, List, Union
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
6
|
from urllib.parse import unquote
|
|
7
7
|
|
|
8
8
|
from .exceptions import WebscoutE
|
|
@@ -16,7 +16,7 @@ except ImportError:
|
|
|
16
16
|
|
|
17
17
|
REGEX_STRIP_TAGS = re.compile("<.*?>")
|
|
18
18
|
|
|
19
|
-
def _expand_proxy_tb_alias(proxy: str
|
|
19
|
+
def _expand_proxy_tb_alias(proxy: Optional[str]) -> Optional[str]:
|
|
20
20
|
"""Expand "tb" to a full proxy URL if applicable."""
|
|
21
21
|
return "socks5://127.0.0.1:9150" if proxy == "tb" else proxy
|
|
22
22
|
|
webscout/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "7.
|
|
1
|
+
__version__ = "7.9"
|
|
2
2
|
__prog__ = "webscout"
|
webscout/webscout_search.py
CHANGED
|
@@ -14,6 +14,8 @@ from threading import Event
|
|
|
14
14
|
from time import sleep, time
|
|
15
15
|
from types import TracebackType
|
|
16
16
|
from typing import Any, cast
|
|
17
|
+
import os
|
|
18
|
+
from typing import Literal, Iterator
|
|
17
19
|
|
|
18
20
|
import primp # type: ignore
|
|
19
21
|
|
|
@@ -45,15 +47,26 @@ class WEBS:
|
|
|
45
47
|
|
|
46
48
|
_executor: ThreadPoolExecutor = ThreadPoolExecutor()
|
|
47
49
|
_impersonates = (
|
|
48
|
-
"chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106", "chrome_107",
|
|
49
|
-
"chrome_109", "chrome_114", "chrome_116", "chrome_117", "chrome_118",
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", "
|
|
53
|
-
"
|
|
50
|
+
"chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106", "chrome_107",
|
|
51
|
+
"chrome_108", "chrome_109", "chrome_114", "chrome_116", "chrome_117", "chrome_118",
|
|
52
|
+
"chrome_119", "chrome_120", "chrome_123", "chrome_124", "chrome_126", "chrome_127",
|
|
53
|
+
"chrome_128", "chrome_129", "chrome_130", "chrome_131", "chrome_133",
|
|
54
|
+
"safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", "safari_ios_18.1.1",
|
|
55
|
+
"safari_15.3", "safari_15.5", "safari_15.6.1", "safari_16", "safari_16.5",
|
|
56
|
+
"safari_17.0", "safari_17.2.1", "safari_17.4.1", "safari_17.5",
|
|
57
|
+
"safari_18", "safari_18.2",
|
|
54
58
|
"safari_ipad_18",
|
|
55
|
-
"edge_101", "edge_122", "edge_127",
|
|
59
|
+
"edge_101", "edge_122", "edge_127", "edge_131",
|
|
60
|
+
"firefox_109", "firefox_117", "firefox_128", "firefox_133", "firefox_135",
|
|
56
61
|
) # fmt: skip
|
|
62
|
+
_impersonates_os = ("android", "ios", "linux", "macos", "windows")
|
|
63
|
+
_chat_models = {
|
|
64
|
+
"gpt-4o-mini": "gpt-4o-mini",
|
|
65
|
+
"llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
66
|
+
"claude-3-haiku": "claude-3-haiku-20240307",
|
|
67
|
+
"o3-mini": "o3-mini",
|
|
68
|
+
"mistral-small-3": "mistralai/Mistral-Small-24B-Instruct-2501",
|
|
69
|
+
}
|
|
57
70
|
|
|
58
71
|
def __init__(
|
|
59
72
|
self,
|
|
@@ -61,6 +74,7 @@ class WEBS:
|
|
|
61
74
|
proxy: str | None = None,
|
|
62
75
|
proxies: dict[str, str] | str | None = None, # deprecated
|
|
63
76
|
timeout: int | None = 10,
|
|
77
|
+
verify: bool = True,
|
|
64
78
|
) -> None:
|
|
65
79
|
"""Initialize the WEBS object.
|
|
66
80
|
|
|
@@ -69,8 +83,10 @@ class WEBS:
|
|
|
69
83
|
proxy (str, optional): proxy for the HTTP client, supports http/https/socks5 protocols.
|
|
70
84
|
example: "http://user:pass@example.com:3128". Defaults to None.
|
|
71
85
|
timeout (int, optional): Timeout value for the HTTP client. Defaults to 10.
|
|
86
|
+
verify (bool): SSL verification when making the request. Defaults to True.
|
|
72
87
|
"""
|
|
73
|
-
|
|
88
|
+
ddgs_proxy: str | None = os.environ.get("DDGS_PROXY")
|
|
89
|
+
self.proxy: str | None = ddgs_proxy if ddgs_proxy else _expand_proxy_tb_alias(proxy)
|
|
74
90
|
assert self.proxy is None or isinstance(self.proxy, str), "proxy must be a str"
|
|
75
91
|
if not proxy and proxies:
|
|
76
92
|
warnings.warn("'proxies' is deprecated, use 'proxy' instead.", stacklevel=1)
|
|
@@ -100,15 +116,19 @@ class WEBS:
|
|
|
100
116
|
cookie_store=True,
|
|
101
117
|
referer=True,
|
|
102
118
|
impersonate=choice(self._impersonates),
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
impersonate_os=choice(self._impersonates_os),
|
|
120
|
+
follow_redirects=False,
|
|
121
|
+
verify=verify,
|
|
105
122
|
)
|
|
123
|
+
self.timeout = timeout
|
|
106
124
|
self.sleep_timestamp = 0.0
|
|
107
125
|
|
|
108
126
|
self._exception_event = Event()
|
|
109
127
|
self._chat_messages: list[dict[str, str]] = []
|
|
110
128
|
self._chat_tokens_count = 0
|
|
111
129
|
self._chat_vqd: str = ""
|
|
130
|
+
self._chat_vqd_hash: str = ""
|
|
131
|
+
self._chat_xfe: str = ""
|
|
112
132
|
|
|
113
133
|
def __enter__(self) -> WEBS:
|
|
114
134
|
return self
|
|
@@ -126,114 +146,153 @@ class WEBS:
|
|
|
126
146
|
"""Get HTML parser."""
|
|
127
147
|
return LHTMLParser(remove_blank_text=True, remove_comments=True, remove_pis=True, collect_ids=False)
|
|
128
148
|
|
|
129
|
-
def _sleep(self, sleeptime: float =
|
|
149
|
+
def _sleep(self, sleeptime: float = 0.75) -> None:
|
|
130
150
|
"""Sleep between API requests."""
|
|
131
|
-
delay =
|
|
151
|
+
delay = 0.0 if not self.sleep_timestamp else 0.0 if time() - self.sleep_timestamp >= 20 else sleeptime
|
|
132
152
|
self.sleep_timestamp = time()
|
|
133
153
|
sleep(delay)
|
|
134
154
|
|
|
135
155
|
def _get_url(
|
|
136
156
|
self,
|
|
137
|
-
method:
|
|
157
|
+
method: Literal["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", "PATCH"],
|
|
138
158
|
url: str,
|
|
139
159
|
params: dict[str, str] | None = None,
|
|
140
160
|
content: bytes | None = None,
|
|
141
161
|
data: dict[str, str] | None = None,
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
headers: dict[str, str] | None = None,
|
|
163
|
+
cookies: dict[str, str] | None = None,
|
|
164
|
+
json: Any = None,
|
|
165
|
+
timeout: float | None = None,
|
|
166
|
+
) -> Any:
|
|
144
167
|
self._sleep()
|
|
145
168
|
try:
|
|
146
|
-
resp = self.client.request(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
resp = self.client.request(
|
|
170
|
+
method,
|
|
171
|
+
url,
|
|
172
|
+
params=params,
|
|
173
|
+
content=content,
|
|
174
|
+
data=data,
|
|
175
|
+
headers=headers,
|
|
176
|
+
cookies=cookies,
|
|
177
|
+
json=json,
|
|
178
|
+
timeout=timeout or self.timeout,
|
|
179
|
+
)
|
|
153
180
|
except Exception as ex:
|
|
154
181
|
if "time" in str(ex).lower():
|
|
155
182
|
raise TimeoutE(f"{url} {type(ex).__name__}: {ex}") from ex
|
|
156
183
|
raise WebscoutE(f"{url} {type(ex).__name__}: {ex}") from ex
|
|
157
|
-
|
|
158
184
|
if resp.status_code == 200:
|
|
159
|
-
return resp
|
|
160
|
-
elif resp.status_code in (202, 301, 403, 429,
|
|
161
|
-
raise RatelimitE(f"{url} {resp.status_code} Ratelimit
|
|
162
|
-
raise WebscoutE(f"{url} return None. {params=} {content=} {data=}")
|
|
185
|
+
return resp
|
|
186
|
+
elif resp.status_code in (202, 301, 403, 400, 429, 418):
|
|
187
|
+
raise RatelimitE(f"{resp.url} {resp.status_code} Ratelimit")
|
|
188
|
+
raise WebscoutE(f"{resp.url} return None. {params=} {content=} {data=}")
|
|
163
189
|
|
|
164
190
|
def _get_vqd(self, keywords: str) -> str:
|
|
165
191
|
"""Get vqd value for a search query."""
|
|
166
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com", params={"q": keywords})
|
|
192
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com", params={"q": keywords}).content
|
|
167
193
|
return _extract_vqd(resp_content, keywords)
|
|
168
194
|
|
|
169
|
-
def
|
|
195
|
+
def chat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> Iterator[str]:
|
|
170
196
|
"""Initiates a chat session with webscout AI.
|
|
171
197
|
|
|
172
198
|
Args:
|
|
173
199
|
keywords (str): The initial message or question to send to the AI.
|
|
174
|
-
model (str): The model to use: "gpt-4o-mini", "
|
|
175
|
-
Defaults to "gpt-4o-mini".
|
|
200
|
+
model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
|
|
201
|
+
"o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
|
|
176
202
|
timeout (int): Timeout value for the HTTP client. Defaults to 20.
|
|
177
203
|
|
|
178
|
-
|
|
179
|
-
str:
|
|
204
|
+
Yields:
|
|
205
|
+
str: Chunks of the response from the AI.
|
|
180
206
|
"""
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
207
|
+
# x-fe-version
|
|
208
|
+
if not self._chat_xfe:
|
|
209
|
+
resp_content = self._get_url(
|
|
210
|
+
method="GET",
|
|
211
|
+
url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
|
|
212
|
+
).content
|
|
213
|
+
try:
|
|
214
|
+
xfe1 = resp_content.split(b'__DDG_BE_VERSION__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
|
|
215
|
+
xfe2 = resp_content.split(b'__DDG_FE_CHAT_HASH__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
|
|
216
|
+
self._chat_xfe = f"{xfe1}-{xfe2}"
|
|
217
|
+
except Exception as ex:
|
|
218
|
+
raise WebscoutE(
|
|
219
|
+
f"chat_yield() Error to get _chat_xfe: {type(ex).__name__}: {ex}"
|
|
220
|
+
) from ex
|
|
196
221
|
# vqd
|
|
197
222
|
if not self._chat_vqd:
|
|
198
|
-
resp = self.
|
|
223
|
+
resp = self._get_url(
|
|
224
|
+
method="GET", url="https://duckduckgo.com/duckchat/v1/status", headers={"x-vqd-accept": "1"}
|
|
225
|
+
)
|
|
199
226
|
self._chat_vqd = resp.headers.get("x-vqd-4", "")
|
|
227
|
+
self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
|
|
200
228
|
|
|
201
229
|
self._chat_messages.append({"role": "user", "content": keywords})
|
|
202
|
-
self._chat_tokens_count += len(keywords) // 4
|
|
203
|
-
|
|
230
|
+
self._chat_tokens_count += max(len(keywords) // 4, 1) # approximate number of tokens
|
|
231
|
+
if model not in self._chat_models:
|
|
232
|
+
warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
|
|
233
|
+
model = "gpt-4o-mini"
|
|
204
234
|
json_data = {
|
|
205
|
-
"model":
|
|
235
|
+
"model": self._chat_models[model],
|
|
206
236
|
"messages": self._chat_messages,
|
|
207
237
|
}
|
|
208
|
-
resp = self.
|
|
209
|
-
"
|
|
210
|
-
|
|
238
|
+
resp = self._get_url(
|
|
239
|
+
method="POST",
|
|
240
|
+
url="https://duckduckgo.com/duckchat/v1/chat",
|
|
241
|
+
headers={
|
|
242
|
+
"x-fe-version": self._chat_xfe,
|
|
243
|
+
"x-vqd-4": self._chat_vqd,
|
|
244
|
+
"x-vqd-hash-1": "",
|
|
245
|
+
},
|
|
211
246
|
json=json_data,
|
|
212
247
|
timeout=timeout,
|
|
213
248
|
)
|
|
214
249
|
self._chat_vqd = resp.headers.get("x-vqd-4", "")
|
|
250
|
+
self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
|
|
251
|
+
chunks = []
|
|
252
|
+
try:
|
|
253
|
+
for chunk in resp.stream():
|
|
254
|
+
lines = chunk.split(b"data:")
|
|
255
|
+
for line in lines:
|
|
256
|
+
if line := line.strip():
|
|
257
|
+
if line == b"[DONE]":
|
|
258
|
+
break
|
|
259
|
+
if line == b"[DONE][LIMIT_CONVERSATION]":
|
|
260
|
+
raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
|
|
261
|
+
x = json_loads(line)
|
|
262
|
+
if isinstance(x, dict):
|
|
263
|
+
if x.get("action") == "error":
|
|
264
|
+
err_message = x.get("type", "")
|
|
265
|
+
if x.get("status") == 429:
|
|
266
|
+
raise (
|
|
267
|
+
ConversationLimitException(err_message)
|
|
268
|
+
if err_message == "ERR_CONVERSATION_LIMIT"
|
|
269
|
+
else RatelimitE(err_message)
|
|
270
|
+
)
|
|
271
|
+
raise WebscoutE(err_message)
|
|
272
|
+
elif message := x.get("message"):
|
|
273
|
+
chunks.append(message)
|
|
274
|
+
yield message
|
|
275
|
+
except Exception as ex:
|
|
276
|
+
raise WebscoutE(f"chat_yield() {type(ex).__name__}: {ex}") from ex
|
|
215
277
|
|
|
216
|
-
|
|
217
|
-
|
|
278
|
+
result = "".join(chunks)
|
|
279
|
+
self._chat_messages.append({"role": "assistant", "content": result})
|
|
280
|
+
self._chat_tokens_count += len(result)
|
|
218
281
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if x.get("action") == "error":
|
|
222
|
-
err_message = x.get("type", "")
|
|
223
|
-
if x.get("status") == 429:
|
|
224
|
-
raise (
|
|
225
|
-
ConversationLimitException(err_message)
|
|
226
|
-
if err_message == "ERR_CONVERSATION_LIMIT"
|
|
227
|
-
else RatelimitE(err_message)
|
|
228
|
-
)
|
|
229
|
-
raise WebscoutE(err_message)
|
|
230
|
-
elif message := x.get("message"):
|
|
231
|
-
results.append(message)
|
|
232
|
-
result = "".join(results)
|
|
282
|
+
def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) -> str:
|
|
283
|
+
"""Initiates a chat session with webscout AI.
|
|
233
284
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
285
|
+
Args:
|
|
286
|
+
keywords (str): The initial message or question to send to the AI.
|
|
287
|
+
model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
|
|
288
|
+
"o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
|
|
289
|
+
timeout (int): Timeout value for the HTTP client. Defaults to 30.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
str: The response from the AI.
|
|
293
|
+
"""
|
|
294
|
+
answer_generator = self.chat_yield(keywords, model, timeout)
|
|
295
|
+
return "".join(answer_generator)
|
|
237
296
|
|
|
238
297
|
def text(
|
|
239
298
|
self,
|
|
@@ -339,7 +398,7 @@ class WEBS:
|
|
|
339
398
|
|
|
340
399
|
def _text_api_page(s: int) -> list[dict[str, str]]:
|
|
341
400
|
payload["s"] = f"{s}"
|
|
342
|
-
resp_content = self._get_url("GET", "https://links.duckduckgo.com/d.js", params=payload)
|
|
401
|
+
resp_content = self._get_url("GET", "https://links.duckduckgo.com/d.js", params=payload).content
|
|
343
402
|
page_data = _text_extract_json(resp_content, keywords)
|
|
344
403
|
page_results = []
|
|
345
404
|
for row in page_data:
|
|
@@ -413,7 +472,7 @@ class WEBS:
|
|
|
413
472
|
|
|
414
473
|
def _text_html_page(s: int) -> list[dict[str, str]]:
|
|
415
474
|
payload["s"] = f"{s}"
|
|
416
|
-
resp_content = self._get_url("POST", "https://html.duckduckgo.com/html", data=payload)
|
|
475
|
+
resp_content = self._get_url("POST", "https://html.duckduckgo.com/html", data=payload).content
|
|
417
476
|
if b"No results." in resp_content:
|
|
418
477
|
return []
|
|
419
478
|
|
|
@@ -500,7 +559,7 @@ class WEBS:
|
|
|
500
559
|
|
|
501
560
|
def _text_lite_page(s: int) -> list[dict[str, str]]:
|
|
502
561
|
payload["s"] = f"{s}"
|
|
503
|
-
resp_content = self._get_url("POST", "https://lite.duckduckgo.com/lite/", data=payload)
|
|
562
|
+
resp_content = self._get_url("POST", "https://lite.duckduckgo.com/lite/", data=payload).content
|
|
504
563
|
if b"No more results." in resp_content:
|
|
505
564
|
return []
|
|
506
565
|
|
|
@@ -621,7 +680,7 @@ class WEBS:
|
|
|
621
680
|
|
|
622
681
|
def _images_page(s: int) -> list[dict[str, str]]:
|
|
623
682
|
payload["s"] = f"{s}"
|
|
624
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com/i.js", params=payload)
|
|
683
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com/i.js", params=payload).content
|
|
625
684
|
resp_json = json_loads(resp_content)
|
|
626
685
|
|
|
627
686
|
page_data = resp_json.get("results", [])
|
|
@@ -708,7 +767,7 @@ class WEBS:
|
|
|
708
767
|
|
|
709
768
|
def _videos_page(s: int) -> list[dict[str, str]]:
|
|
710
769
|
payload["s"] = f"{s}"
|
|
711
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com/v.js", params=payload)
|
|
770
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com/v.js", params=payload).content
|
|
712
771
|
resp_json = json_loads(resp_content)
|
|
713
772
|
|
|
714
773
|
page_data = resp_json.get("results", [])
|
|
@@ -777,7 +836,7 @@ class WEBS:
|
|
|
777
836
|
|
|
778
837
|
def _news_page(s: int) -> list[dict[str, str]]:
|
|
779
838
|
payload["s"] = f"{s}"
|
|
780
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com/news.js", params=payload)
|
|
839
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com/news.js", params=payload).content
|
|
781
840
|
resp_json = json_loads(resp_content)
|
|
782
841
|
page_data = resp_json.get("results", [])
|
|
783
842
|
page_results = []
|
|
@@ -828,7 +887,7 @@ class WEBS:
|
|
|
828
887
|
"q": f"what is {keywords}",
|
|
829
888
|
"format": "json",
|
|
830
889
|
}
|
|
831
|
-
resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload)
|
|
890
|
+
resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload).content
|
|
832
891
|
page_data = json_loads(resp_content)
|
|
833
892
|
|
|
834
893
|
results = []
|
|
@@ -849,7 +908,7 @@ class WEBS:
|
|
|
849
908
|
"q": f"{keywords}",
|
|
850
909
|
"format": "json",
|
|
851
910
|
}
|
|
852
|
-
resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload)
|
|
911
|
+
resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload).content
|
|
853
912
|
resp_json = json_loads(resp_content)
|
|
854
913
|
page_data = resp_json.get("RelatedTopics", [])
|
|
855
914
|
|
|
@@ -900,7 +959,7 @@ class WEBS:
|
|
|
900
959
|
"q": keywords,
|
|
901
960
|
"kl": region,
|
|
902
961
|
}
|
|
903
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com/ac/", params=payload)
|
|
962
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com/ac/", params=payload).content
|
|
904
963
|
page_data = json_loads(resp_content)
|
|
905
964
|
return [r for r in page_data]
|
|
906
965
|
|
|
@@ -986,7 +1045,7 @@ class WEBS:
|
|
|
986
1045
|
"GET",
|
|
987
1046
|
"https://nominatim.openstreetmap.org/search.php",
|
|
988
1047
|
params=params,
|
|
989
|
-
)
|
|
1048
|
+
).content
|
|
990
1049
|
if resp_content == b"[]":
|
|
991
1050
|
raise WebscoutE("maps() Coordinates are not found, check function parameters.")
|
|
992
1051
|
resp_json = json_loads(resp_content)
|
|
@@ -1022,7 +1081,7 @@ class WEBS:
|
|
|
1022
1081
|
"bbox_br": f"{lat_b},{lon_r}",
|
|
1023
1082
|
"strict_bbox": "1",
|
|
1024
1083
|
}
|
|
1025
|
-
resp_content = self._get_url("GET", "https://duckduckgo.com/local.js", params=params)
|
|
1084
|
+
resp_content = self._get_url("GET", "https://duckduckgo.com/local.js", params=params).content
|
|
1026
1085
|
resp_json = json_loads(resp_content)
|
|
1027
1086
|
page_data = resp_json.get("results", [])
|
|
1028
1087
|
|
|
@@ -1127,7 +1186,7 @@ class WEBS:
|
|
|
1127
1186
|
"https://duckduckgo.com/translation.js",
|
|
1128
1187
|
params=payload,
|
|
1129
1188
|
content=keyword.encode(),
|
|
1130
|
-
)
|
|
1189
|
+
).content
|
|
1131
1190
|
page_data: dict[str, str] = json_loads(resp_content)
|
|
1132
1191
|
page_data["original"] = keyword
|
|
1133
1192
|
return page_data
|
|
@@ -1167,7 +1226,7 @@ class WEBS:
|
|
|
1167
1226
|
lang = language.split('-')[0]
|
|
1168
1227
|
url = f"https://duckduckgo.com/js/spice/forecast/{quote(location)}/{lang}"
|
|
1169
1228
|
|
|
1170
|
-
resp = self._get_url("GET", url)
|
|
1229
|
+
resp = self._get_url("GET", url).content
|
|
1171
1230
|
resp_text = resp.decode('utf-8')
|
|
1172
1231
|
|
|
1173
1232
|
if "ddg_spice_forecast(" not in resp_text:
|