webscout 8.2.6__py3-none-any.whl → 8.2.8__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 +1 -1
- webscout/AIutel.py +298 -239
- webscout/Extra/Act.md +309 -0
- webscout/Extra/GitToolkit/gitapi/README.md +110 -0
- webscout/Extra/YTToolkit/README.md +375 -0
- webscout/Extra/YTToolkit/ytapi/README.md +44 -0
- webscout/Extra/YTToolkit/ytapi/extras.py +92 -19
- webscout/Extra/autocoder/autocoder.py +309 -114
- webscout/Extra/autocoder/autocoder_utiles.py +15 -15
- webscout/Extra/gguf.md +430 -0
- webscout/Extra/tempmail/README.md +488 -0
- webscout/Extra/weather.md +281 -0
- webscout/Litlogger/Readme.md +175 -0
- webscout/Provider/AISEARCH/DeepFind.py +41 -37
- webscout/Provider/AISEARCH/README.md +279 -0
- webscout/Provider/AISEARCH/__init__.py +0 -1
- webscout/Provider/AISEARCH/genspark_search.py +228 -86
- webscout/Provider/AISEARCH/hika_search.py +11 -11
- webscout/Provider/AISEARCH/scira_search.py +324 -322
- webscout/Provider/AllenAI.py +7 -14
- webscout/Provider/Blackboxai.py +518 -74
- webscout/Provider/Cloudflare.py +0 -1
- webscout/Provider/Deepinfra.py +23 -21
- webscout/Provider/Flowith.py +217 -0
- webscout/Provider/FreeGemini.py +250 -0
- webscout/Provider/GizAI.py +15 -5
- webscout/Provider/Glider.py +11 -8
- webscout/Provider/HeckAI.py +80 -52
- webscout/Provider/Koboldai.py +7 -4
- webscout/Provider/LambdaChat.py +2 -2
- webscout/Provider/Marcus.py +10 -18
- webscout/Provider/OPENAI/BLACKBOXAI.py +735 -0
- webscout/Provider/OPENAI/Cloudflare.py +378 -0
- webscout/Provider/OPENAI/FreeGemini.py +282 -0
- webscout/Provider/OPENAI/NEMOTRON.py +244 -0
- webscout/Provider/OPENAI/README.md +1253 -0
- webscout/Provider/OPENAI/__init__.py +8 -0
- webscout/Provider/OPENAI/ai4chat.py +293 -286
- webscout/Provider/OPENAI/api.py +810 -0
- webscout/Provider/OPENAI/base.py +217 -14
- webscout/Provider/OPENAI/c4ai.py +373 -367
- webscout/Provider/OPENAI/chatgpt.py +7 -0
- webscout/Provider/OPENAI/chatgptclone.py +7 -0
- webscout/Provider/OPENAI/chatsandbox.py +172 -0
- webscout/Provider/OPENAI/deepinfra.py +30 -20
- webscout/Provider/OPENAI/e2b.py +6 -0
- webscout/Provider/OPENAI/exaai.py +7 -0
- webscout/Provider/OPENAI/exachat.py +6 -0
- webscout/Provider/OPENAI/flowith.py +162 -0
- webscout/Provider/OPENAI/freeaichat.py +359 -352
- webscout/Provider/OPENAI/glider.py +323 -316
- webscout/Provider/OPENAI/groq.py +361 -354
- webscout/Provider/OPENAI/heckai.py +30 -64
- webscout/Provider/OPENAI/llmchatco.py +8 -0
- webscout/Provider/OPENAI/mcpcore.py +7 -0
- webscout/Provider/OPENAI/multichat.py +8 -0
- webscout/Provider/OPENAI/netwrck.py +356 -350
- webscout/Provider/OPENAI/opkfc.py +8 -0
- webscout/Provider/OPENAI/scirachat.py +471 -462
- webscout/Provider/OPENAI/sonus.py +9 -0
- webscout/Provider/OPENAI/standardinput.py +9 -1
- webscout/Provider/OPENAI/textpollinations.py +339 -329
- webscout/Provider/OPENAI/toolbaz.py +7 -0
- webscout/Provider/OPENAI/typefully.py +355 -0
- webscout/Provider/OPENAI/typegpt.py +358 -346
- webscout/Provider/OPENAI/uncovrAI.py +7 -0
- webscout/Provider/OPENAI/utils.py +103 -7
- webscout/Provider/OPENAI/venice.py +12 -0
- webscout/Provider/OPENAI/wisecat.py +19 -19
- webscout/Provider/OPENAI/writecream.py +7 -0
- webscout/Provider/OPENAI/x0gpt.py +7 -0
- webscout/Provider/OPENAI/yep.py +50 -21
- webscout/Provider/OpenGPT.py +1 -1
- webscout/Provider/TTI/AiForce/README.md +159 -0
- webscout/Provider/TTI/FreeAIPlayground/README.md +99 -0
- webscout/Provider/TTI/ImgSys/README.md +174 -0
- webscout/Provider/TTI/MagicStudio/README.md +101 -0
- webscout/Provider/TTI/Nexra/README.md +155 -0
- webscout/Provider/TTI/PollinationsAI/README.md +146 -0
- webscout/Provider/TTI/README.md +128 -0
- webscout/Provider/TTI/aiarta/README.md +134 -0
- webscout/Provider/TTI/artbit/README.md +100 -0
- webscout/Provider/TTI/fastflux/README.md +129 -0
- webscout/Provider/TTI/huggingface/README.md +114 -0
- webscout/Provider/TTI/piclumen/README.md +161 -0
- webscout/Provider/TTI/pixelmuse/README.md +79 -0
- webscout/Provider/TTI/talkai/README.md +139 -0
- webscout/Provider/TTS/README.md +192 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/speechma.py +500 -100
- webscout/Provider/TTS/sthir.py +94 -0
- webscout/Provider/TeachAnything.py +3 -7
- webscout/Provider/TextPollinationsAI.py +4 -2
- webscout/Provider/{aimathgpt.py → UNFINISHED/ChatHub.py} +88 -68
- webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
- webscout/Provider/UNFINISHED/oivscode.py +351 -0
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
- webscout/Provider/Writecream.py +11 -2
- webscout/Provider/__init__.py +8 -14
- webscout/Provider/ai4chat.py +4 -58
- webscout/Provider/asksteve.py +17 -9
- webscout/Provider/cerebras.py +3 -1
- webscout/Provider/koala.py +170 -268
- webscout/Provider/llmchat.py +3 -0
- webscout/Provider/lmarena.py +198 -0
- webscout/Provider/meta.py +7 -4
- webscout/Provider/samurai.py +223 -0
- webscout/Provider/scira_chat.py +4 -2
- webscout/Provider/typefully.py +23 -151
- webscout/__init__.py +4 -2
- webscout/cli.py +3 -28
- webscout/conversation.py +35 -35
- webscout/litagent/Readme.md +276 -0
- webscout/scout/README.md +402 -0
- webscout/swiftcli/Readme.md +323 -0
- webscout/version.py +1 -1
- webscout/webscout_search.py +2 -182
- webscout/webscout_search_async.py +1 -179
- webscout/zeroart/README.md +89 -0
- webscout/zeroart/__init__.py +134 -54
- webscout/zeroart/base.py +19 -13
- webscout/zeroart/effects.py +101 -99
- webscout/zeroart/fonts.py +1239 -816
- {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/METADATA +116 -74
- {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/RECORD +130 -103
- {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/WHEEL +1 -1
- webscout-8.2.8.dist-info/entry_points.txt +3 -0
- webscout-8.2.8.dist-info/top_level.txt +1 -0
- webscout/Provider/AISEARCH/ISou.py +0 -256
- webscout/Provider/ElectronHub.py +0 -773
- webscout/Provider/Free2GPT.py +0 -241
- webscout/Provider/GPTWeb.py +0 -249
- webscout/Provider/bagoodex.py +0 -145
- webscout/Provider/geminiprorealtime.py +0 -160
- webscout/scout/core.py +0 -881
- webscout-8.2.6.dist-info/entry_points.txt +0 -3
- webscout-8.2.6.dist-info/top_level.txt +0 -2
- webstoken/__init__.py +0 -30
- webstoken/classifier.py +0 -189
- webstoken/keywords.py +0 -216
- webstoken/language.py +0 -128
- webstoken/ner.py +0 -164
- webstoken/normalizer.py +0 -35
- webstoken/processor.py +0 -77
- webstoken/sentiment.py +0 -206
- webstoken/stemmer.py +0 -73
- webstoken/tagger.py +0 -60
- webstoken/tokenizer.py +0 -158
- /webscout/Provider/{Youchat.py → UNFINISHED/Youchat.py} +0 -0
- {webscout-8.2.6.dist-info → webscout-8.2.8.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ SwiftCLI
|
|
4
|
+
|
|
5
|
+
> Build Beautiful Command-Line Applications at Light Speed
|
|
6
|
+
|
|
7
|
+
[](https://www.python.org)
|
|
8
|
+
[](https://pypi.org/project/webscout/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
## 🌟 Key Features
|
|
15
|
+
|
|
16
|
+
- 🎨 **Rich Output**: Beautiful tables, progress bars, and styled text
|
|
17
|
+
- 🔄 **Command Groups**: Organize commands logically
|
|
18
|
+
- 🎯 **Type Safety**: Full type hints and runtime validation
|
|
19
|
+
- 🔌 **Plugin System**: Extend functionality easily
|
|
20
|
+
- 🌍 **Environment Support**: Load config from env vars and files
|
|
21
|
+
- 🚀 **Modern Python**: Async support, type hints, and more
|
|
22
|
+
|
|
23
|
+
## 📦 Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -U webscout
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 🚀 Quick Start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from webscout.swiftcli import CLI, option, table_output
|
|
33
|
+
|
|
34
|
+
app = CLI("myapp", version="1.0.0")
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
@option("--count", "-c", type=int, default=5)
|
|
38
|
+
@table_output(["ID", "Status"])
|
|
39
|
+
def list_items(count: int):
|
|
40
|
+
"""List system items with status"""
|
|
41
|
+
return [
|
|
42
|
+
[i, "Active" if i % 2 == 0 else "Inactive"]
|
|
43
|
+
for i in range(1, count + 1)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app.run()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Run it:
|
|
51
|
+
```bash
|
|
52
|
+
$ python app.py list-items --count 3
|
|
53
|
+
┌────┬──────────┐
|
|
54
|
+
│ ID │ Status │
|
|
55
|
+
├────┼──────────┤
|
|
56
|
+
│ 1 │ Inactive │
|
|
57
|
+
│ 2 │ Active │
|
|
58
|
+
│ 3 │ Inactive │
|
|
59
|
+
└────┴──────────┘
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 📚 Documentation
|
|
63
|
+
|
|
64
|
+
### Command Groups
|
|
65
|
+
|
|
66
|
+
Organize related commands:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
@app.group()
|
|
70
|
+
def db():
|
|
71
|
+
"""Database operations"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
@db.command()
|
|
75
|
+
@option("--force", is_flag=True)
|
|
76
|
+
def migrate(force: bool):
|
|
77
|
+
"""Run database migrations"""
|
|
78
|
+
print(f"Running migrations (force={force})")
|
|
79
|
+
|
|
80
|
+
# Usage: python app.py db migrate --force
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Rich Output
|
|
84
|
+
|
|
85
|
+
Beautiful progress bars and tables:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
@app.command()
|
|
89
|
+
@progress("Processing")
|
|
90
|
+
async def process():
|
|
91
|
+
"""Process items with progress"""
|
|
92
|
+
for i in range(5):
|
|
93
|
+
yield f"Step {i+1}/5"
|
|
94
|
+
await asyncio.sleep(0.5)
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
@table_output(["Name", "Score"])
|
|
98
|
+
def top_scores():
|
|
99
|
+
"""Show top scores"""
|
|
100
|
+
return [
|
|
101
|
+
["Alice", 100],
|
|
102
|
+
["Bob", 95],
|
|
103
|
+
["Charlie", 90]
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Type-Safe Options
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from enum import Enum
|
|
111
|
+
from datetime import datetime
|
|
112
|
+
from typing import List, Optional
|
|
113
|
+
|
|
114
|
+
class Format(Enum):
|
|
115
|
+
JSON = "json"
|
|
116
|
+
YAML = "yaml"
|
|
117
|
+
CSV = "csv"
|
|
118
|
+
|
|
119
|
+
@app.command()
|
|
120
|
+
@option("--format", type=Format, default=Format.JSON)
|
|
121
|
+
@option("--date", type=datetime)
|
|
122
|
+
@option("--tags", type=List[str])
|
|
123
|
+
def export(
|
|
124
|
+
format: Format,
|
|
125
|
+
date: datetime,
|
|
126
|
+
tags: Optional[List[str]] = None
|
|
127
|
+
):
|
|
128
|
+
"""Export data with type validation"""
|
|
129
|
+
print(f"Exporting as {format.value}")
|
|
130
|
+
print(f"Date: {date}")
|
|
131
|
+
if tags:
|
|
132
|
+
print(f"Tags: {', '.join(tags)}")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Environment & Config
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
@app.command()
|
|
139
|
+
@envvar("API_KEY", required=True)
|
|
140
|
+
@config_file("~/.config/myapp.yaml")
|
|
141
|
+
def api_call(api_key: str, config: dict):
|
|
142
|
+
"""Make API call using config"""
|
|
143
|
+
url = config.get("api_url")
|
|
144
|
+
print(f"Calling {url} with key {api_key[:4]}...")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Async Support
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
@app.command()
|
|
151
|
+
async def fetch_data():
|
|
152
|
+
"""Fetch data asynchronously"""
|
|
153
|
+
async with aiohttp.ClientSession() as session:
|
|
154
|
+
async with session.get("https://api.example.com") as resp:
|
|
155
|
+
data = await resp.json()
|
|
156
|
+
return data
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Plugin System
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from webscout.swiftcli import Plugin
|
|
163
|
+
|
|
164
|
+
class MetricsPlugin(Plugin):
|
|
165
|
+
def __init__(self):
|
|
166
|
+
self.start_time = None
|
|
167
|
+
|
|
168
|
+
def before_command(self, command: str, args: list):
|
|
169
|
+
self.start_time = time.time()
|
|
170
|
+
|
|
171
|
+
def after_command(self, command: str, args: list, result: any):
|
|
172
|
+
duration = time.time() - self.start_time
|
|
173
|
+
print(f"Command {command} took {duration:.2f}s")
|
|
174
|
+
|
|
175
|
+
app.plugin_manager.register(MetricsPlugin())
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 🛠 Advanced Features
|
|
179
|
+
|
|
180
|
+
### Custom Output Formatting
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from rich.console import Console
|
|
184
|
+
from rich.panel import Panel
|
|
185
|
+
from rich.table import Table
|
|
186
|
+
|
|
187
|
+
console = Console()
|
|
188
|
+
|
|
189
|
+
@app.command()
|
|
190
|
+
def status():
|
|
191
|
+
"""Show system status"""
|
|
192
|
+
table = Table(show_header=True)
|
|
193
|
+
table.add_column("Service")
|
|
194
|
+
table.add_column("Status")
|
|
195
|
+
table.add_column("Uptime")
|
|
196
|
+
|
|
197
|
+
table.add_row("API", "✅ Online", "24h")
|
|
198
|
+
table.add_row("DB", "✅ Online", "12h")
|
|
199
|
+
table.add_row("Cache", "⚠️ Degraded", "6h")
|
|
200
|
+
|
|
201
|
+
console.print(Panel(
|
|
202
|
+
table,
|
|
203
|
+
title="System Status",
|
|
204
|
+
border_style="green"
|
|
205
|
+
))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Command Pipelines
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
@app.group(chain=True)
|
|
212
|
+
def process():
|
|
213
|
+
"""Data processing pipeline"""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
@process.command()
|
|
217
|
+
def extract():
|
|
218
|
+
"""Extract data"""
|
|
219
|
+
return {"data": [1, 2, 3]}
|
|
220
|
+
|
|
221
|
+
@process.command()
|
|
222
|
+
def transform(data: dict):
|
|
223
|
+
"""Transform data"""
|
|
224
|
+
return {"data": [x * 2 for x in data["data"]]}
|
|
225
|
+
|
|
226
|
+
@process.command()
|
|
227
|
+
def load(data: dict):
|
|
228
|
+
"""Load data"""
|
|
229
|
+
print(f"Loading: {data}")
|
|
230
|
+
|
|
231
|
+
# Usage: python app.py process extract transform load
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 🔧 Configuration
|
|
235
|
+
|
|
236
|
+
### Application Config
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
app = CLI(
|
|
240
|
+
name="myapp",
|
|
241
|
+
version="1.0.0",
|
|
242
|
+
description="My awesome CLI app",
|
|
243
|
+
config_file="~/.config/myapp.yaml",
|
|
244
|
+
auto_envvar_prefix="MYAPP",
|
|
245
|
+
plugin_folder="~/.myapp/plugins"
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Command Config
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
@app.command()
|
|
253
|
+
@option("--config", type=click.Path(exists=True))
|
|
254
|
+
@option("--verbose", "-v", count=True)
|
|
255
|
+
@option("--format", type=click.Choice(["json", "yaml"]))
|
|
256
|
+
def process(config: str, verbose: int, format: str):
|
|
257
|
+
"""Process with configuration"""
|
|
258
|
+
pass
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## 📋 Best Practices
|
|
262
|
+
|
|
263
|
+
1. **Use Type Hints**
|
|
264
|
+
```python
|
|
265
|
+
from typing import Optional, List, Dict
|
|
266
|
+
|
|
267
|
+
@app.command()
|
|
268
|
+
def search(
|
|
269
|
+
query: str,
|
|
270
|
+
limit: Optional[int] = 10,
|
|
271
|
+
tags: List[str] = None
|
|
272
|
+
) -> Dict[str, any]:
|
|
273
|
+
"""Search with proper type hints"""
|
|
274
|
+
pass
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
2. **Structured Error Handling**
|
|
278
|
+
```python
|
|
279
|
+
from webscout.swiftcli import CLIError
|
|
280
|
+
|
|
281
|
+
@app.command()
|
|
282
|
+
def risky():
|
|
283
|
+
try:
|
|
284
|
+
# Risky operation
|
|
285
|
+
pass
|
|
286
|
+
except FileNotFoundError as e:
|
|
287
|
+
raise CLIError("Config file not found") from e
|
|
288
|
+
except PermissionError as e:
|
|
289
|
+
raise CLIError("Permission denied") from e
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
3. **Command Organization**
|
|
293
|
+
```python
|
|
294
|
+
# commands/
|
|
295
|
+
# ├── __init__.py
|
|
296
|
+
# ├── db.py
|
|
297
|
+
# ├── auth.py
|
|
298
|
+
# └── utils.py
|
|
299
|
+
|
|
300
|
+
from .commands import db, auth, utils
|
|
301
|
+
|
|
302
|
+
app.add_command_group(db.commands)
|
|
303
|
+
app.add_command_group(auth.commands)
|
|
304
|
+
app.add_command_group(utils.commands)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## 🤝 Contributing
|
|
308
|
+
|
|
309
|
+
Contributions are welcome! Check out our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
310
|
+
|
|
311
|
+
## 📝 License
|
|
312
|
+
|
|
313
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
314
|
+
|
|
315
|
+
<div align="center">
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
Made with ❤️ by the [Webscout](https://github.com/OEvortex/Webscout) team
|
|
320
|
+
|
|
321
|
+
[](https://github.com/OEvortex/Webscout)
|
|
322
|
+
|
|
323
|
+
</div>
|
webscout/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "8.2.
|
|
1
|
+
__version__ = "8.2.8"
|
|
2
2
|
__prog__ = "webscout"
|
webscout/webscout_search.py
CHANGED
|
@@ -56,13 +56,7 @@ class WEBS:
|
|
|
56
56
|
"firefox133", "firefox135",
|
|
57
57
|
) # fmt: skip
|
|
58
58
|
_impersonates_os = ("android", "ios", "linux", "macos", "windows")
|
|
59
|
-
|
|
60
|
-
"gpt-4o-mini": "gpt-4o-mini",
|
|
61
|
-
"llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
62
|
-
"claude-3-haiku": "claude-3-haiku-20240307",
|
|
63
|
-
"o3-mini": "o3-mini",
|
|
64
|
-
"mistral-small-3": "mistralai/Mistral-Small-24B-Instruct-2501",
|
|
65
|
-
}
|
|
59
|
+
|
|
66
60
|
|
|
67
61
|
def __init__(
|
|
68
62
|
self,
|
|
@@ -119,11 +113,6 @@ class WEBS:
|
|
|
119
113
|
self.sleep_timestamp = 0.0
|
|
120
114
|
|
|
121
115
|
self._exception_event = Event()
|
|
122
|
-
self._chat_messages: list[dict[str, str]] = []
|
|
123
|
-
self._chat_tokens_count = 0
|
|
124
|
-
self._chat_vqd: str = ""
|
|
125
|
-
self._chat_vqd_hash: str = ""
|
|
126
|
-
self._chat_xfe: str = ""
|
|
127
116
|
|
|
128
117
|
def __enter__(self) -> WEBS:
|
|
129
118
|
return self
|
|
@@ -203,176 +192,7 @@ class WEBS:
|
|
|
203
192
|
resp_content = self._get_url("GET", "https://duckduckgo.com", params={"q": keywords}).content
|
|
204
193
|
return _extract_vqd(resp_content, keywords)
|
|
205
194
|
|
|
206
|
-
def chat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> Iterator[str]:
|
|
207
|
-
"""Initiates a chat session with webscout AI.
|
|
208
195
|
|
|
209
|
-
Args:
|
|
210
|
-
keywords (str): The initial message or question to send to the AI.
|
|
211
|
-
model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
|
|
212
|
-
"o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
|
|
213
|
-
timeout (int): Timeout value for the HTTP client. Defaults to 30.
|
|
214
|
-
max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
|
|
215
|
-
|
|
216
|
-
Yields:
|
|
217
|
-
str: Chunks of the response from the AI.
|
|
218
|
-
"""
|
|
219
|
-
# Get Cloudflare Turnstile token
|
|
220
|
-
def get_turnstile_token():
|
|
221
|
-
try:
|
|
222
|
-
# Visit the DuckDuckGo chat page to get the Turnstile token
|
|
223
|
-
resp_content = self._get_url(
|
|
224
|
-
method="GET",
|
|
225
|
-
url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
|
|
226
|
-
).content
|
|
227
|
-
|
|
228
|
-
# Extract the Turnstile token if available
|
|
229
|
-
if b'cf-turnstile-response' in resp_content:
|
|
230
|
-
token = resp_content.split(b'cf-turnstile-response="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
|
|
231
|
-
return token
|
|
232
|
-
return ""
|
|
233
|
-
except Exception:
|
|
234
|
-
return ""
|
|
235
|
-
|
|
236
|
-
# x-fe-version
|
|
237
|
-
if not self._chat_xfe:
|
|
238
|
-
resp_content = self._get_url(
|
|
239
|
-
method="GET",
|
|
240
|
-
url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
|
|
241
|
-
).content
|
|
242
|
-
try:
|
|
243
|
-
xfe1 = resp_content.split(b'__DDG_BE_VERSION__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
|
|
244
|
-
xfe2 = resp_content.split(b'__DDG_FE_CHAT_HASH__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
|
|
245
|
-
self._chat_xfe = f"{xfe1}-{xfe2}"
|
|
246
|
-
except Exception as ex:
|
|
247
|
-
raise WebscoutE(
|
|
248
|
-
f"chat_yield() Error to get _chat_xfe: {type(ex).__name__}: {ex}"
|
|
249
|
-
) from ex
|
|
250
|
-
# vqd
|
|
251
|
-
if not self._chat_vqd:
|
|
252
|
-
resp = self._get_url(
|
|
253
|
-
method="GET", url="https://duckduckgo.com/duckchat/v1/status", headers={"x-vqd-accept": "1"}
|
|
254
|
-
)
|
|
255
|
-
self._chat_vqd = resp.headers.get("x-vqd-4", "")
|
|
256
|
-
self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
|
|
257
|
-
|
|
258
|
-
self._chat_messages.append({"role": "user", "content": keywords})
|
|
259
|
-
self._chat_tokens_count += max(len(keywords) // 4, 1) # approximate number of tokens
|
|
260
|
-
if model not in self._chat_models:
|
|
261
|
-
warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
|
|
262
|
-
model = "gpt-4o-mini"
|
|
263
|
-
|
|
264
|
-
# Get Cloudflare Turnstile token
|
|
265
|
-
turnstile_token = get_turnstile_token()
|
|
266
|
-
|
|
267
|
-
json_data = {
|
|
268
|
-
"model": self._chat_models[model],
|
|
269
|
-
"messages": self._chat_messages,
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
# Add Turnstile token if available
|
|
273
|
-
if turnstile_token:
|
|
274
|
-
json_data["cf-turnstile-response"] = turnstile_token
|
|
275
|
-
|
|
276
|
-
# Enhanced headers to better mimic a real browser
|
|
277
|
-
chat_headers = {
|
|
278
|
-
"x-fe-version": self._chat_xfe,
|
|
279
|
-
"x-vqd-4": self._chat_vqd,
|
|
280
|
-
"x-vqd-hash-1": "",
|
|
281
|
-
"Accept": "text/event-stream",
|
|
282
|
-
"Accept-Language": "en-US,en;q=0.9",
|
|
283
|
-
"Cache-Control": "no-cache",
|
|
284
|
-
"Content-Type": "application/json",
|
|
285
|
-
"DNT": "1",
|
|
286
|
-
"Origin": "https://duckduckgo.com",
|
|
287
|
-
"Referer": "https://duckduckgo.com/",
|
|
288
|
-
"Sec-Fetch-Dest": "empty",
|
|
289
|
-
"Sec-Fetch-Mode": "cors",
|
|
290
|
-
"Sec-Fetch-Site": "same-origin",
|
|
291
|
-
"User-Agent": self.client.headers.get("User-Agent", "")
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
# Retry logic for rate limited requests
|
|
295
|
-
retry_count = 0
|
|
296
|
-
while retry_count <= max_retries:
|
|
297
|
-
try:
|
|
298
|
-
resp = self._get_url(
|
|
299
|
-
method="POST",
|
|
300
|
-
url="https://duckduckgo.com/duckchat/v1/chat",
|
|
301
|
-
headers=chat_headers,
|
|
302
|
-
json=json_data,
|
|
303
|
-
timeout=timeout,
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
self._chat_vqd = resp.headers.get("x-vqd-4", "")
|
|
307
|
-
self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
|
|
308
|
-
chunks = []
|
|
309
|
-
|
|
310
|
-
# curl_cffi uses iter_content instead of stream
|
|
311
|
-
for chunk in resp.iter_content(chunk_size=1024):
|
|
312
|
-
lines = chunk.split(b"data:")
|
|
313
|
-
for line in lines:
|
|
314
|
-
if line := line.strip():
|
|
315
|
-
if line == b"[DONE]":
|
|
316
|
-
break
|
|
317
|
-
if line == b"[DONE][LIMIT_CONVERSATION]":
|
|
318
|
-
raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
|
|
319
|
-
try:
|
|
320
|
-
x = json_loads(line)
|
|
321
|
-
if isinstance(x, dict):
|
|
322
|
-
if x.get("action") == "error":
|
|
323
|
-
err_message = x.get("type", "")
|
|
324
|
-
if x.get("status") == 429:
|
|
325
|
-
raise (
|
|
326
|
-
ConversationLimitException(err_message)
|
|
327
|
-
if err_message == "ERR_CONVERSATION_LIMIT"
|
|
328
|
-
else RatelimitE(err_message)
|
|
329
|
-
)
|
|
330
|
-
raise WebscoutE(err_message)
|
|
331
|
-
elif message := x.get("message"):
|
|
332
|
-
chunks.append(message)
|
|
333
|
-
yield message
|
|
334
|
-
except Exception as e:
|
|
335
|
-
# Skip invalid JSON data
|
|
336
|
-
continue
|
|
337
|
-
|
|
338
|
-
# If we get here, the request was successful
|
|
339
|
-
result = "".join(chunks)
|
|
340
|
-
self._chat_messages.append({"role": "assistant", "content": result})
|
|
341
|
-
self._chat_tokens_count += len(result)
|
|
342
|
-
return
|
|
343
|
-
|
|
344
|
-
except RatelimitE as ex:
|
|
345
|
-
retry_count += 1
|
|
346
|
-
if retry_count > max_retries:
|
|
347
|
-
raise WebscoutE(f"chat_yield() Rate limit exceeded after {max_retries} retries: {ex}") from ex
|
|
348
|
-
|
|
349
|
-
# Get a fresh Turnstile token for the retry
|
|
350
|
-
turnstile_token = get_turnstile_token()
|
|
351
|
-
if turnstile_token:
|
|
352
|
-
json_data["cf-turnstile-response"] = turnstile_token
|
|
353
|
-
|
|
354
|
-
# Exponential backoff
|
|
355
|
-
sleep_time = 2 ** retry_count
|
|
356
|
-
sleep(sleep_time)
|
|
357
|
-
|
|
358
|
-
except Exception as ex:
|
|
359
|
-
raise WebscoutE(f"chat_yield() {type(ex).__name__}: {ex}") from ex
|
|
360
|
-
|
|
361
|
-
def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> str:
|
|
362
|
-
"""Initiates a chat session with webscout AI.
|
|
363
|
-
|
|
364
|
-
Args:
|
|
365
|
-
keywords (str): The initial message or question to send to the AI.
|
|
366
|
-
model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
|
|
367
|
-
"o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
|
|
368
|
-
timeout (int): Timeout value for the HTTP client. Defaults to 30.
|
|
369
|
-
max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
|
|
370
|
-
|
|
371
|
-
Returns:
|
|
372
|
-
str: The response from the AI.
|
|
373
|
-
"""
|
|
374
|
-
answer_generator = self.chat_yield(keywords, model, timeout, max_retries)
|
|
375
|
-
return "".join(answer_generator)
|
|
376
196
|
|
|
377
197
|
def text(
|
|
378
198
|
self,
|
|
@@ -1361,4 +1181,4 @@ class WEBS:
|
|
|
1361
1181
|
"visibility_m": hour.get("visibility"),
|
|
1362
1182
|
})
|
|
1363
1183
|
|
|
1364
|
-
return formatted_data
|
|
1184
|
+
return formatted_data
|