webscout 6.2b0__py3-none-any.whl → 6.4__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 +191 -176
- webscout/AIbase.py +112 -239
- webscout/AIutel.py +488 -1130
- webscout/Agents/functioncall.py +248 -198
- webscout/Bing_search.py +250 -153
- webscout/DWEBS.py +454 -178
- webscout/Extra/__init__.py +2 -1
- webscout/Extra/autocoder/__init__.py +9 -0
- webscout/Extra/autocoder/autocoder_utiles.py +121 -0
- webscout/Extra/autocoder/rawdog.py +681 -0
- webscout/Extra/autollama.py +246 -195
- webscout/Extra/gguf.py +441 -226
- webscout/Extra/weather.py +172 -67
- webscout/LLM.py +442 -100
- webscout/Litlogger/__init__.py +681 -0
- webscout/Local/formats.py +4 -2
- webscout/Provider/Amigo.py +19 -10
- webscout/Provider/Andi.py +0 -33
- webscout/Provider/Blackboxai.py +4 -204
- webscout/Provider/DARKAI.py +1 -1
- webscout/Provider/EDITEE.py +1 -1
- webscout/Provider/Llama3.py +1 -1
- webscout/Provider/Marcus.py +137 -0
- webscout/Provider/NinjaChat.py +1 -1
- webscout/Provider/PI.py +221 -207
- webscout/Provider/Perplexity.py +598 -598
- webscout/Provider/RoboCoders.py +206 -0
- webscout/Provider/TTI/AiForce/__init__.py +22 -0
- webscout/Provider/TTI/AiForce/async_aiforce.py +257 -0
- webscout/Provider/TTI/AiForce/sync_aiforce.py +242 -0
- webscout/Provider/TTI/Nexra/__init__.py +22 -0
- webscout/Provider/TTI/Nexra/async_nexra.py +286 -0
- webscout/Provider/TTI/Nexra/sync_nexra.py +258 -0
- webscout/Provider/TTI/PollinationsAI/__init__.py +23 -0
- webscout/Provider/TTI/PollinationsAI/async_pollinations.py +330 -0
- webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +285 -0
- webscout/Provider/TTI/__init__.py +3 -4
- webscout/Provider/TTI/artbit/__init__.py +22 -0
- webscout/Provider/TTI/artbit/async_artbit.py +184 -0
- webscout/Provider/TTI/artbit/sync_artbit.py +176 -0
- webscout/Provider/TTI/blackbox/__init__.py +4 -0
- webscout/Provider/TTI/blackbox/async_blackbox.py +212 -0
- webscout/Provider/TTI/{blackboximage.py → blackbox/sync_blackbox.py} +199 -153
- webscout/Provider/TTI/deepinfra/__init__.py +4 -0
- webscout/Provider/TTI/deepinfra/async_deepinfra.py +227 -0
- webscout/Provider/TTI/deepinfra/sync_deepinfra.py +199 -0
- webscout/Provider/TTI/huggingface/__init__.py +22 -0
- webscout/Provider/TTI/huggingface/async_huggingface.py +199 -0
- webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -0
- webscout/Provider/TTI/imgninza/__init__.py +4 -0
- webscout/Provider/TTI/imgninza/async_ninza.py +214 -0
- webscout/Provider/TTI/{imgninza.py → imgninza/sync_ninza.py} +209 -136
- webscout/Provider/TTI/talkai/__init__.py +4 -0
- webscout/Provider/TTI/talkai/async_talkai.py +229 -0
- webscout/Provider/TTI/talkai/sync_talkai.py +207 -0
- webscout/Provider/__init__.py +146 -132
- webscout/Provider/askmyai.py +158 -0
- webscout/Provider/cerebras.py +227 -206
- webscout/Provider/geminiapi.py +208 -198
- webscout/Provider/llama3mitril.py +180 -0
- webscout/Provider/llmchat.py +203 -0
- webscout/Provider/mhystical.py +176 -0
- webscout/Provider/perplexitylabs.py +265 -0
- webscout/Provider/talkai.py +196 -0
- webscout/Provider/twitterclone.py +251 -244
- webscout/Provider/typegpt.py +359 -0
- webscout/__init__.py +28 -23
- webscout/__main__.py +5 -5
- webscout/cli.py +327 -347
- webscout/conversation.py +227 -0
- webscout/exceptions.py +161 -29
- webscout/litagent/__init__.py +172 -0
- webscout/litprinter/__init__.py +831 -0
- webscout/optimizers.py +270 -0
- webscout/prompt_manager.py +279 -0
- webscout/swiftcli/__init__.py +810 -0
- webscout/transcriber.py +479 -551
- webscout/update_checker.py +125 -0
- webscout/version.py +1 -1
- webscout-6.4.dist-info/LICENSE.md +211 -0
- {webscout-6.2b0.dist-info → webscout-6.4.dist-info}/METADATA +34 -55
- webscout-6.4.dist-info/RECORD +154 -0
- webscout/Provider/TTI/AIuncensored.py +0 -103
- webscout/Provider/TTI/Nexra.py +0 -120
- webscout/Provider/TTI/PollinationsAI.py +0 -138
- webscout/Provider/TTI/WebSimAI.py +0 -142
- webscout/Provider/TTI/aiforce.py +0 -160
- webscout/Provider/TTI/artbit.py +0 -141
- webscout/Provider/TTI/deepinfra.py +0 -148
- webscout/Provider/TTI/huggingface.py +0 -155
- webscout/models.py +0 -23
- webscout-6.2b0.dist-info/LICENSE.md +0 -50
- webscout-6.2b0.dist-info/RECORD +0 -118
- /webscout/{g4f.py → gpt4free.py} +0 -0
- {webscout-6.2b0.dist-info → webscout-6.4.dist-info}/WHEEL +0 -0
- {webscout-6.2b0.dist-info → webscout-6.4.dist-info}/entry_points.txt +0 -0
- {webscout-6.2b0.dist-info → webscout-6.4.dist-info}/top_level.txt +0 -0
webscout/AIutel.py
CHANGED
|
@@ -1,1130 +1,488 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
import platform
|
|
4
|
-
import subprocess
|
|
5
|
-
import logging
|
|
6
|
-
import threading
|
|
7
|
-
import time
|
|
8
|
-
import appdirs
|
|
9
|
-
import datetime
|
|
10
|
-
import re
|
|
11
|
-
import sys
|
|
12
|
-
import click
|
|
13
|
-
from rich.markdown import Markdown
|
|
14
|
-
from rich.console import Console
|
|
15
|
-
import g4f
|
|
16
|
-
from typing import List, Tuple, Union
|
|
17
|
-
from typing import NoReturn
|
|
18
|
-
import requests
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
from playsound import playsound
|
|
21
|
-
from time import sleep as wait
|
|
22
|
-
import pathlib
|
|
23
|
-
import urllib.parse
|
|
24
|
-
appdir = appdirs.AppDirs("AIWEBS", "webscout")
|
|
25
|
-
|
|
26
|
-
default_path = appdir.user_cache_dir
|
|
27
|
-
|
|
28
|
-
if not os.path.exists(default_path):
|
|
29
|
-
os.makedirs(default_path)
|
|
30
|
-
webai = [
|
|
31
|
-
"leo",
|
|
32
|
-
"openai",
|
|
33
|
-
"opengpt",
|
|
34
|
-
"koboldai",
|
|
35
|
-
"gemini",
|
|
36
|
-
"phind",
|
|
37
|
-
"blackboxai",
|
|
38
|
-
"g4fauto",
|
|
39
|
-
"perplexity",
|
|
40
|
-
"groq",
|
|
41
|
-
"reka",
|
|
42
|
-
"cohere",
|
|
43
|
-
"yepchat",
|
|
44
|
-
"you",
|
|
45
|
-
"xjai",
|
|
46
|
-
"thinkany",
|
|
47
|
-
"berlin4h",
|
|
48
|
-
"chatgptuk",
|
|
49
|
-
"auto",
|
|
50
|
-
"poe",
|
|
51
|
-
"basedgpt",
|
|
52
|
-
"deepseek",
|
|
53
|
-
"deepinfra",
|
|
54
|
-
"vtlchat",
|
|
55
|
-
"geminiflash",
|
|
56
|
-
"geminipro",
|
|
57
|
-
"ollama",
|
|
58
|
-
"andi",
|
|
59
|
-
"llama3"
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
gpt4free_providers = [
|
|
63
|
-
provider.__name__ for provider in g4f.Provider.__providers__ # if provider.working
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
available_providers = webai + gpt4free_providers
|
|
67
|
-
def sanitize_stream(
|
|
68
|
-
chunk: str, intro_value: str = "data:", to_json: bool = True
|
|
69
|
-
) -> str | dict:
|
|
70
|
-
"""Remove streaming flags
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
chunk (str): Streamig chunk.
|
|
74
|
-
intro_value (str, optional): streaming flag. Defaults to "data:".
|
|
75
|
-
to_json (bool, optional). Return chunk as dictionary. Defaults to True.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
str: Sanitized streaming value.
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
if chunk.startswith(intro_value):
|
|
82
|
-
chunk = chunk[len(intro_value) :]
|
|
83
|
-
|
|
84
|
-
return json.loads(chunk) if to_json else chunk
|
|
85
|
-
def run_system_command(
|
|
86
|
-
command: str,
|
|
87
|
-
exit_on_error: bool = True,
|
|
88
|
-
stdout_error: bool = True,
|
|
89
|
-
help: str = None,
|
|
90
|
-
):
|
|
91
|
-
"""Run commands against system
|
|
92
|
-
Args:
|
|
93
|
-
command (str): shell command
|
|
94
|
-
exit_on_error (bool, optional): Exit on error. Defaults to True.
|
|
95
|
-
stdout_error (bool, optional): Print out the error. Defaults to True
|
|
96
|
-
help (str, optional): Help info incase of exception. Defaults to None.
|
|
97
|
-
Returns:
|
|
98
|
-
tuple : (is_successfull, object[Exception|Subprocess.run])
|
|
99
|
-
"""
|
|
100
|
-
try:
|
|
101
|
-
# Run the command and capture the output
|
|
102
|
-
result = subprocess.run(
|
|
103
|
-
command,
|
|
104
|
-
shell=True,
|
|
105
|
-
check=True,
|
|
106
|
-
text=True,
|
|
107
|
-
stdout=subprocess.PIPE,
|
|
108
|
-
stderr=subprocess.PIPE,
|
|
109
|
-
)
|
|
110
|
-
return (True, result)
|
|
111
|
-
except subprocess.CalledProcessError as e:
|
|
112
|
-
# Handle error if the command returns a non-zero exit code
|
|
113
|
-
if stdout_error:
|
|
114
|
-
click.secho(f"Error Occurred: while running '{command}'", fg="yellow")
|
|
115
|
-
click.secho(e.stderr, fg="red")
|
|
116
|
-
if help is not None:
|
|
117
|
-
click.secho(help, fg="cyan")
|
|
118
|
-
sys.exit(e.returncode) if exit_on_error else None
|
|
119
|
-
return (False, e)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
""
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
""
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
""
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
""
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
""
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
""
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
""
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
"
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
""
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
""
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
""
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
""
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
""
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"""
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"""Check Webscout latest version info
|
|
490
|
-
|
|
491
|
-
Args:
|
|
492
|
-
whole (bool, optional): Return whole json response. Defaults to False.
|
|
493
|
-
version (bool, optional): return version only. Defaults to False.
|
|
494
|
-
|
|
495
|
-
Returns:
|
|
496
|
-
bool|dict: version str or whole dict info
|
|
497
|
-
"""
|
|
498
|
-
import requests
|
|
499
|
-
|
|
500
|
-
data = requests.get(self.url).json()
|
|
501
|
-
if whole:
|
|
502
|
-
return data
|
|
503
|
-
|
|
504
|
-
elif version:
|
|
505
|
-
return data.get("tag_name")
|
|
506
|
-
|
|
507
|
-
else:
|
|
508
|
-
sorted = dict(
|
|
509
|
-
tag_name=data.get("tag_name"),
|
|
510
|
-
tarball_url=data.get("tarball_url"),
|
|
511
|
-
zipball_url=data.get("zipball_url"),
|
|
512
|
-
html_url=data.get("html_url"),
|
|
513
|
-
body=data.get("body"),
|
|
514
|
-
)
|
|
515
|
-
whole_assets = []
|
|
516
|
-
for entry in data.get("assets"):
|
|
517
|
-
url = entry.get("browser_download_url")
|
|
518
|
-
assets = dict(url=url, size=entry.get("size"))
|
|
519
|
-
if ".deb" in url:
|
|
520
|
-
assets["target"] = "Debian"
|
|
521
|
-
elif ".exe" in url:
|
|
522
|
-
assets["target"] = "Windows"
|
|
523
|
-
elif "macos" in url:
|
|
524
|
-
assets["target"] = "Mac"
|
|
525
|
-
elif "linux" in url:
|
|
526
|
-
assets["target"] = "Linux"
|
|
527
|
-
|
|
528
|
-
whole_assets.append(assets)
|
|
529
|
-
sorted["assets"] = whole_assets
|
|
530
|
-
|
|
531
|
-
return sorted
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
class RawDog:
|
|
535
|
-
"""Generate and auto-execute Python scripts in the cli"""
|
|
536
|
-
|
|
537
|
-
examples = """\
|
|
538
|
-
EXAMPLES:
|
|
539
|
-
|
|
540
|
-
1. User: Kill the process running on port 3000
|
|
541
|
-
|
|
542
|
-
LLM:
|
|
543
|
-
```python
|
|
544
|
-
import os
|
|
545
|
-
os.system("kill $(lsof -t -i:3000)")
|
|
546
|
-
print("Process killed")
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
2. User: Summarize my essay
|
|
550
|
-
|
|
551
|
-
LLM:
|
|
552
|
-
```python
|
|
553
|
-
import glob
|
|
554
|
-
files = glob.glob("*essay*.*")
|
|
555
|
-
with open(files[0], "r") as f:
|
|
556
|
-
print(f.read())
|
|
557
|
-
```
|
|
558
|
-
CONTINUE
|
|
559
|
-
|
|
560
|
-
User:
|
|
561
|
-
LAST SCRIPT OUTPUT:
|
|
562
|
-
John Smith
|
|
563
|
-
Essay 2021-09-01
|
|
564
|
-
...
|
|
565
|
-
|
|
566
|
-
LLM:
|
|
567
|
-
```python
|
|
568
|
-
print("The essay is about...")
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
3. User: Weather in qazigund
|
|
572
|
-
|
|
573
|
-
LLM:
|
|
574
|
-
```python
|
|
575
|
-
from webscout import weather as w
|
|
576
|
-
weather = w.get("Qazigund")
|
|
577
|
-
w.print_weather(weather)
|
|
578
|
-
```
|
|
579
|
-
"""
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
def __init__(
|
|
583
|
-
self,
|
|
584
|
-
quiet: bool = False,
|
|
585
|
-
internal_exec: bool = False,
|
|
586
|
-
confirm_script: bool = False,
|
|
587
|
-
interpreter: str = "python",
|
|
588
|
-
prettify: bool = True,
|
|
589
|
-
):
|
|
590
|
-
"""Constructor
|
|
591
|
-
|
|
592
|
-
Args:
|
|
593
|
-
quiet (bool, optional): Flag for control logging. Defaults to False.
|
|
594
|
-
internal_exec (bool, optional): Execute scripts with exec function. Defaults to False.
|
|
595
|
-
confirm_script (bool, optional): Give consent to scripts prior to execution. Defaults to False.
|
|
596
|
-
interpreter (str, optional): Python's interpreter name. Defaults to Python.
|
|
597
|
-
prettify (bool, optional): Prettify the code on stdout. Defaults to True.
|
|
598
|
-
"""
|
|
599
|
-
# if not quiet:
|
|
600
|
-
# print(
|
|
601
|
-
# "Rawdog is an experimental tool that generates and auto-executes Python scripts in the cli.\n"
|
|
602
|
-
# "To get the most out of Rawdog. Ensure the following are installed:\n"
|
|
603
|
-
# " 1. Python 3.x\n"
|
|
604
|
-
# " 2. Dependency:\n"
|
|
605
|
-
# " - Matplotlib\n"
|
|
606
|
-
# "Be alerted on the risk posed! (Experimental)\n"
|
|
607
|
-
# "Use '--quiet' to suppress this message and code/logs stdout.\n"
|
|
608
|
-
# )
|
|
609
|
-
self.internal_exec = internal_exec
|
|
610
|
-
self.confirm_script = confirm_script
|
|
611
|
-
self.quiet = quiet
|
|
612
|
-
self.interpreter = interpreter
|
|
613
|
-
self.prettify = prettify
|
|
614
|
-
self.python_version = (
|
|
615
|
-
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
616
|
-
if self.internal_exec
|
|
617
|
-
else run_system_command(
|
|
618
|
-
f"{self.interpreter} --version",
|
|
619
|
-
exit_on_error=True,
|
|
620
|
-
stdout_error=True,
|
|
621
|
-
help="If you're using Webscout-cli, use the flag '--internal-exec'",
|
|
622
|
-
)[1].stdout.split(" ")[1]
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
@property
|
|
626
|
-
def intro_prompt(self):
|
|
627
|
-
return f"""
|
|
628
|
-
You are a command-line coding assistant called Rawdog that generates and auto-executes Python scripts.
|
|
629
|
-
|
|
630
|
-
A typical interaction goes like this:
|
|
631
|
-
1. The user gives you a natural language PROMPT.
|
|
632
|
-
2. You:
|
|
633
|
-
i. Determine what needs to be done
|
|
634
|
-
ii. Write a short Python SCRIPT to do it
|
|
635
|
-
iii. Communicate back to the user by printing to the console in that SCRIPT
|
|
636
|
-
3. The compiler extracts the script and then runs it using exec(). If there will be an exception raised,
|
|
637
|
-
it will be send back to you starting with "PREVIOUS SCRIPT EXCEPTION:".
|
|
638
|
-
4. In case of exception, regenerate error free script.
|
|
639
|
-
|
|
640
|
-
If you need to review script outputs before completing the task, you can print the word "CONTINUE" at the end of your SCRIPT.
|
|
641
|
-
This can be useful for summarizing documents or technical readouts, reading instructions before
|
|
642
|
-
deciding what to do, or other tasks that require multi-step reasoning.
|
|
643
|
-
A typical 'CONTINUE' interaction looks like this:
|
|
644
|
-
1. The user gives you a natural language PROMPT.
|
|
645
|
-
2. You:
|
|
646
|
-
i. Determine what needs to be done
|
|
647
|
-
ii. Determine that you need to see the output of some subprocess call to complete the task
|
|
648
|
-
iii. Write a short Python SCRIPT to print that and then print the word "CONTINUE"
|
|
649
|
-
3. The compiler
|
|
650
|
-
i. Checks and runs your SCRIPT
|
|
651
|
-
ii. Captures the output and appends it to the conversation as "LAST SCRIPT OUTPUT:"
|
|
652
|
-
iii. Finds the word "CONTINUE" and sends control back to you
|
|
653
|
-
4. You again:
|
|
654
|
-
i. Look at the original PROMPT + the "LAST SCRIPT OUTPUT:" to determine what needs to be done
|
|
655
|
-
ii. Write a short Python SCRIPT to do it
|
|
656
|
-
iii. Communicate back to the user by printing to the console in that SCRIPT
|
|
657
|
-
5. The compiler...
|
|
658
|
-
|
|
659
|
-
Please follow these conventions carefully:
|
|
660
|
-
- Decline any tasks that seem dangerous, irreversible, or that you don't understand.
|
|
661
|
-
- Always review the full conversation prior to answering and maintain continuity.
|
|
662
|
-
- If asked for information, just print the information clearly and concisely.
|
|
663
|
-
- If asked to do something, print a concise summary of what you've done as confirmation.
|
|
664
|
-
- If asked a question, respond in a friendly, conversational way. Use programmatically-generated and natural language responses as appropriate.
|
|
665
|
-
- If you need clarification, return a SCRIPT that prints your question. In the next interaction, continue based on the user's response.
|
|
666
|
-
- Assume the user would like something concise. For example rather than printing a massive table, filter or summarize it to what's likely of interest.
|
|
667
|
-
- Actively clean up any temporary processes or files you use.
|
|
668
|
-
- When looking through files, use git as available to skip files, and skip hidden files (.env, .git, etc) by default.
|
|
669
|
-
- You can plot anything with matplotlib.
|
|
670
|
-
- ALWAYS Return your SCRIPT inside of a single pair of ``` delimiters. Only the console output of the first such SCRIPT is visible to the user, so make sure that it's complete and don't bother returning anything else.
|
|
671
|
-
|
|
672
|
-
{self.examples}
|
|
673
|
-
|
|
674
|
-
Current system : {platform.system()}
|
|
675
|
-
Python version : {self.python_version}
|
|
676
|
-
Current directory : {os.getcwd()}
|
|
677
|
-
Current Datetime : {datetime.datetime.now()}
|
|
678
|
-
"""
|
|
679
|
-
|
|
680
|
-
def stdout(self, message: str) -> None:
|
|
681
|
-
"""Stdout data
|
|
682
|
-
|
|
683
|
-
Args:
|
|
684
|
-
message (str): Text to be printed
|
|
685
|
-
"""
|
|
686
|
-
if self.prettify:
|
|
687
|
-
Console().print(Markdown(message))
|
|
688
|
-
else:
|
|
689
|
-
click.secho(message, fg="yellow")
|
|
690
|
-
|
|
691
|
-
def log(self, message: str, category: str = "info"):
|
|
692
|
-
"""RawDog logger
|
|
693
|
-
|
|
694
|
-
Args:
|
|
695
|
-
message (str): Log message
|
|
696
|
-
category (str, optional): Log level. Defaults to 'info'.
|
|
697
|
-
"""
|
|
698
|
-
if self.quiet:
|
|
699
|
-
return
|
|
700
|
-
|
|
701
|
-
message = "[Webscout] - " + message
|
|
702
|
-
if category == "error":
|
|
703
|
-
logging.error(message)
|
|
704
|
-
else:
|
|
705
|
-
logging.info(message)
|
|
706
|
-
|
|
707
|
-
def main(self, response: str):
|
|
708
|
-
"""Exec code in response accordingly
|
|
709
|
-
|
|
710
|
-
Args:
|
|
711
|
-
response: AI response
|
|
712
|
-
|
|
713
|
-
Returns:
|
|
714
|
-
Optional[str]: None if script executed successfully else stdout data
|
|
715
|
-
"""
|
|
716
|
-
code_blocks = re.findall(r"```python.*?```", response, re.DOTALL)
|
|
717
|
-
if len(code_blocks) != 1:
|
|
718
|
-
self.stdout(response)
|
|
719
|
-
|
|
720
|
-
else:
|
|
721
|
-
raw_code = code_blocks[0]
|
|
722
|
-
|
|
723
|
-
if self.confirm_script:
|
|
724
|
-
self.stdout(raw_code)
|
|
725
|
-
if not click.confirm("- Do you wish to execute this"):
|
|
726
|
-
return
|
|
727
|
-
|
|
728
|
-
elif not self.quiet:
|
|
729
|
-
self.stdout(raw_code)
|
|
730
|
-
|
|
731
|
-
raw_code_plus = re.sub(r"(```)(python)?", "", raw_code)
|
|
732
|
-
|
|
733
|
-
if "CONTINUE" in response or not self.internal_exec:
|
|
734
|
-
self.log("Executing script externally")
|
|
735
|
-
path_to_script = os.path.join(default_path, "execute_this.py")
|
|
736
|
-
with open(path_to_script, "w") as fh:
|
|
737
|
-
fh.write(raw_code_plus)
|
|
738
|
-
if "CONTINUE" in response:
|
|
739
|
-
|
|
740
|
-
success, proc = run_system_command(
|
|
741
|
-
f"{self.interpreter} {path_to_script}",
|
|
742
|
-
exit_on_error=False,
|
|
743
|
-
stdout_error=False,
|
|
744
|
-
)
|
|
745
|
-
|
|
746
|
-
if success:
|
|
747
|
-
self.log("Returning success feedback")
|
|
748
|
-
return f"LAST SCRIPT OUTPUT:\n{proc.stdout}"
|
|
749
|
-
else:
|
|
750
|
-
|
|
751
|
-
self.log("Returning error feedback", "error")
|
|
752
|
-
return f"PREVIOUS SCRIPT EXCEPTION:\n{proc.stderr}"
|
|
753
|
-
else:
|
|
754
|
-
os.system(f"{self.interpreter} {path_to_script}")
|
|
755
|
-
|
|
756
|
-
else:
|
|
757
|
-
try:
|
|
758
|
-
self.log("Executing script internally")
|
|
759
|
-
exec(raw_code_plus)
|
|
760
|
-
except Exception as e:
|
|
761
|
-
error_message = str(e)
|
|
762
|
-
self.log(
|
|
763
|
-
f"Exception occurred while executing script. Responding with error: {error_message}",
|
|
764
|
-
"error"
|
|
765
|
-
)
|
|
766
|
-
# Return the exact error message
|
|
767
|
-
return f"PREVIOUS SCRIPT EXCEPTION:\n{error_message}"
|
|
768
|
-
|
|
769
|
-
class Audio:
|
|
770
|
-
# Request headers
|
|
771
|
-
headers: dict[str, str] = {
|
|
772
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
|
|
773
|
-
}
|
|
774
|
-
cache_dir = pathlib.Path("./audio_cache")
|
|
775
|
-
all_voices: list[str] = [
|
|
776
|
-
"Filiz",
|
|
777
|
-
"Astrid",
|
|
778
|
-
"Tatyana",
|
|
779
|
-
"Maxim",
|
|
780
|
-
"Carmen",
|
|
781
|
-
"Ines",
|
|
782
|
-
"Cristiano",
|
|
783
|
-
"Vitoria",
|
|
784
|
-
"Ricardo",
|
|
785
|
-
"Maja",
|
|
786
|
-
"Jan",
|
|
787
|
-
"Jacek",
|
|
788
|
-
"Ewa",
|
|
789
|
-
"Ruben",
|
|
790
|
-
"Lotte",
|
|
791
|
-
"Liv",
|
|
792
|
-
"Seoyeon",
|
|
793
|
-
"Takumi",
|
|
794
|
-
"Mizuki",
|
|
795
|
-
"Giorgio",
|
|
796
|
-
"Carla",
|
|
797
|
-
"Bianca",
|
|
798
|
-
"Karl",
|
|
799
|
-
"Dora",
|
|
800
|
-
"Mathieu",
|
|
801
|
-
"Celine",
|
|
802
|
-
"Chantal",
|
|
803
|
-
"Penelope",
|
|
804
|
-
"Miguel",
|
|
805
|
-
"Mia",
|
|
806
|
-
"Enrique",
|
|
807
|
-
"Conchita",
|
|
808
|
-
"Geraint",
|
|
809
|
-
"Salli",
|
|
810
|
-
"Matthew",
|
|
811
|
-
"Kimberly",
|
|
812
|
-
"Kendra",
|
|
813
|
-
"Justin",
|
|
814
|
-
"Joey",
|
|
815
|
-
"Joanna",
|
|
816
|
-
"Ivy",
|
|
817
|
-
"Raveena",
|
|
818
|
-
"Aditi",
|
|
819
|
-
"Emma",
|
|
820
|
-
"Brian",
|
|
821
|
-
"Amy",
|
|
822
|
-
"Russell",
|
|
823
|
-
"Nicole",
|
|
824
|
-
"Vicki",
|
|
825
|
-
"Marlene",
|
|
826
|
-
"Hans",
|
|
827
|
-
"Naja",
|
|
828
|
-
"Mads",
|
|
829
|
-
"Gwyneth",
|
|
830
|
-
"Zhiyu",
|
|
831
|
-
"es-ES-Standard-A",
|
|
832
|
-
"it-IT-Standard-A",
|
|
833
|
-
"it-IT-Wavenet-A",
|
|
834
|
-
"ja-JP-Standard-A",
|
|
835
|
-
"ja-JP-Wavenet-A",
|
|
836
|
-
"ko-KR-Standard-A",
|
|
837
|
-
"ko-KR-Wavenet-A",
|
|
838
|
-
"pt-BR-Standard-A",
|
|
839
|
-
"tr-TR-Standard-A",
|
|
840
|
-
"sv-SE-Standard-A",
|
|
841
|
-
"nl-NL-Standard-A",
|
|
842
|
-
"nl-NL-Wavenet-A",
|
|
843
|
-
"en-US-Wavenet-A",
|
|
844
|
-
"en-US-Wavenet-B",
|
|
845
|
-
"en-US-Wavenet-C",
|
|
846
|
-
"en-US-Wavenet-D",
|
|
847
|
-
"en-US-Wavenet-E",
|
|
848
|
-
"en-US-Wavenet-F",
|
|
849
|
-
"en-GB-Standard-A",
|
|
850
|
-
"en-GB-Standard-B",
|
|
851
|
-
"en-GB-Standard-C",
|
|
852
|
-
"en-GB-Standard-D",
|
|
853
|
-
"en-GB-Wavenet-A",
|
|
854
|
-
"en-GB-Wavenet-B",
|
|
855
|
-
"en-GB-Wavenet-C",
|
|
856
|
-
"en-GB-Wavenet-D",
|
|
857
|
-
"en-US-Standard-B",
|
|
858
|
-
"en-US-Standard-C",
|
|
859
|
-
"en-US-Standard-D",
|
|
860
|
-
"en-US-Standard-E",
|
|
861
|
-
"de-DE-Standard-A",
|
|
862
|
-
"de-DE-Standard-B",
|
|
863
|
-
"de-DE-Wavenet-A",
|
|
864
|
-
"de-DE-Wavenet-B",
|
|
865
|
-
"de-DE-Wavenet-C",
|
|
866
|
-
"de-DE-Wavenet-D",
|
|
867
|
-
"en-AU-Standard-A",
|
|
868
|
-
"en-AU-Standard-B",
|
|
869
|
-
"en-AU-Wavenet-A",
|
|
870
|
-
"en-AU-Wavenet-B",
|
|
871
|
-
"en-AU-Wavenet-C",
|
|
872
|
-
"en-AU-Wavenet-D",
|
|
873
|
-
"en-AU-Standard-C",
|
|
874
|
-
"en-AU-Standard-D",
|
|
875
|
-
"fr-CA-Standard-A",
|
|
876
|
-
"fr-CA-Standard-B",
|
|
877
|
-
"fr-CA-Standard-C",
|
|
878
|
-
"fr-CA-Standard-D",
|
|
879
|
-
"fr-FR-Standard-C",
|
|
880
|
-
"fr-FR-Standard-D",
|
|
881
|
-
"fr-FR-Wavenet-A",
|
|
882
|
-
"fr-FR-Wavenet-B",
|
|
883
|
-
"fr-FR-Wavenet-C",
|
|
884
|
-
"fr-FR-Wavenet-D",
|
|
885
|
-
"da-DK-Wavenet-A",
|
|
886
|
-
"pl-PL-Wavenet-A",
|
|
887
|
-
"pl-PL-Wavenet-B",
|
|
888
|
-
"pl-PL-Wavenet-C",
|
|
889
|
-
"pl-PL-Wavenet-D",
|
|
890
|
-
"pt-PT-Wavenet-A",
|
|
891
|
-
"pt-PT-Wavenet-B",
|
|
892
|
-
"pt-PT-Wavenet-C",
|
|
893
|
-
"pt-PT-Wavenet-D",
|
|
894
|
-
"ru-RU-Wavenet-A",
|
|
895
|
-
"ru-RU-Wavenet-B",
|
|
896
|
-
"ru-RU-Wavenet-C",
|
|
897
|
-
"ru-RU-Wavenet-D",
|
|
898
|
-
"sk-SK-Wavenet-A",
|
|
899
|
-
"tr-TR-Wavenet-A",
|
|
900
|
-
"tr-TR-Wavenet-B",
|
|
901
|
-
"tr-TR-Wavenet-C",
|
|
902
|
-
"tr-TR-Wavenet-D",
|
|
903
|
-
"tr-TR-Wavenet-E",
|
|
904
|
-
"uk-UA-Wavenet-A",
|
|
905
|
-
"ar-XA-Wavenet-A",
|
|
906
|
-
"ar-XA-Wavenet-B",
|
|
907
|
-
"ar-XA-Wavenet-C",
|
|
908
|
-
"cs-CZ-Wavenet-A",
|
|
909
|
-
"nl-NL-Wavenet-B",
|
|
910
|
-
"nl-NL-Wavenet-C",
|
|
911
|
-
"nl-NL-Wavenet-D",
|
|
912
|
-
"nl-NL-Wavenet-E",
|
|
913
|
-
"en-IN-Wavenet-A",
|
|
914
|
-
"en-IN-Wavenet-B",
|
|
915
|
-
"en-IN-Wavenet-C",
|
|
916
|
-
"fil-PH-Wavenet-A",
|
|
917
|
-
"fi-FI-Wavenet-A",
|
|
918
|
-
"el-GR-Wavenet-A",
|
|
919
|
-
"hi-IN-Wavenet-A",
|
|
920
|
-
"hi-IN-Wavenet-B",
|
|
921
|
-
"hi-IN-Wavenet-C",
|
|
922
|
-
"hu-HU-Wavenet-A",
|
|
923
|
-
"id-ID-Wavenet-A",
|
|
924
|
-
"id-ID-Wavenet-B",
|
|
925
|
-
"id-ID-Wavenet-C",
|
|
926
|
-
"it-IT-Wavenet-B",
|
|
927
|
-
"it-IT-Wavenet-C",
|
|
928
|
-
"it-IT-Wavenet-D",
|
|
929
|
-
"ja-JP-Wavenet-B",
|
|
930
|
-
"ja-JP-Wavenet-C",
|
|
931
|
-
"ja-JP-Wavenet-D",
|
|
932
|
-
"cmn-CN-Wavenet-A",
|
|
933
|
-
"cmn-CN-Wavenet-B",
|
|
934
|
-
"cmn-CN-Wavenet-C",
|
|
935
|
-
"cmn-CN-Wavenet-D",
|
|
936
|
-
"nb-no-Wavenet-E",
|
|
937
|
-
"nb-no-Wavenet-A",
|
|
938
|
-
"nb-no-Wavenet-B",
|
|
939
|
-
"nb-no-Wavenet-C",
|
|
940
|
-
"nb-no-Wavenet-D",
|
|
941
|
-
"vi-VN-Wavenet-A",
|
|
942
|
-
"vi-VN-Wavenet-B",
|
|
943
|
-
"vi-VN-Wavenet-C",
|
|
944
|
-
"vi-VN-Wavenet-D",
|
|
945
|
-
"sr-rs-Standard-A",
|
|
946
|
-
"lv-lv-Standard-A",
|
|
947
|
-
"is-is-Standard-A",
|
|
948
|
-
"bg-bg-Standard-A",
|
|
949
|
-
"af-ZA-Standard-A",
|
|
950
|
-
"Tracy",
|
|
951
|
-
"Danny",
|
|
952
|
-
"Huihui",
|
|
953
|
-
"Yaoyao",
|
|
954
|
-
"Kangkang",
|
|
955
|
-
"HanHan",
|
|
956
|
-
"Zhiwei",
|
|
957
|
-
"Asaf",
|
|
958
|
-
"An",
|
|
959
|
-
"Stefanos",
|
|
960
|
-
"Filip",
|
|
961
|
-
"Ivan",
|
|
962
|
-
"Heidi",
|
|
963
|
-
"Herena",
|
|
964
|
-
"Kalpana",
|
|
965
|
-
"Hemant",
|
|
966
|
-
"Matej",
|
|
967
|
-
"Andika",
|
|
968
|
-
"Rizwan",
|
|
969
|
-
"Lado",
|
|
970
|
-
"Valluvar",
|
|
971
|
-
"Linda",
|
|
972
|
-
"Heather",
|
|
973
|
-
"Sean",
|
|
974
|
-
"Michael",
|
|
975
|
-
"Karsten",
|
|
976
|
-
"Guillaume",
|
|
977
|
-
"Pattara",
|
|
978
|
-
"Jakub",
|
|
979
|
-
"Szabolcs",
|
|
980
|
-
"Hoda",
|
|
981
|
-
"Naayf",
|
|
982
|
-
]
|
|
983
|
-
|
|
984
|
-
@classmethod
|
|
985
|
-
def text_to_audio(
|
|
986
|
-
cls,
|
|
987
|
-
message: str,
|
|
988
|
-
voice: str = "Brian",
|
|
989
|
-
save_to: Union[Path, str] = None,
|
|
990
|
-
auto: bool = True,
|
|
991
|
-
) -> Union[str, bytes]:
|
|
992
|
-
"""
|
|
993
|
-
Text to speech using StreamElements API
|
|
994
|
-
|
|
995
|
-
Parameters:
|
|
996
|
-
message (str): The text to convert to speech
|
|
997
|
-
voice (str, optional): The voice to use for speech synthesis. Defaults to "Brian".
|
|
998
|
-
save_to (bool, optional): Path to save the audio file. Defaults to None.
|
|
999
|
-
auto (bool, optional): Generate filename based on `message` and save to `cls.cache_dir`. Defaults to False.
|
|
1000
|
-
|
|
1001
|
-
Returns:
|
|
1002
|
-
result (Union[str, bytes]): Path to saved contents or audio content.
|
|
1003
|
-
"""
|
|
1004
|
-
assert (
|
|
1005
|
-
voice in cls.all_voices
|
|
1006
|
-
), f"Voice '{voice}' not one of [{', '.join(cls.all_voices)}]"
|
|
1007
|
-
# Base URL for provider API
|
|
1008
|
-
url: str = (
|
|
1009
|
-
f"https://api.streamelements.com/kappa/v2/speech?voice={voice}&text={{{urllib.parse.quote(message)}}}"
|
|
1010
|
-
)
|
|
1011
|
-
resp = requests.get(url=url, headers=cls.headers, stream=True)
|
|
1012
|
-
if not resp.ok:
|
|
1013
|
-
raise Exception(
|
|
1014
|
-
f"Failed to perform the operation - ({resp.status_code}, {resp.reason}) - {resp.text}"
|
|
1015
|
-
)
|
|
1016
|
-
|
|
1017
|
-
def sanitize_filename(path):
|
|
1018
|
-
trash = [
|
|
1019
|
-
"\\",
|
|
1020
|
-
"/",
|
|
1021
|
-
":",
|
|
1022
|
-
"*",
|
|
1023
|
-
"?",
|
|
1024
|
-
'"',
|
|
1025
|
-
"<",
|
|
1026
|
-
"|",
|
|
1027
|
-
">",
|
|
1028
|
-
]
|
|
1029
|
-
for val in trash:
|
|
1030
|
-
path = path.replace(val, "")
|
|
1031
|
-
return path.strip()
|
|
1032
|
-
|
|
1033
|
-
if auto:
|
|
1034
|
-
filename: str = message + "..." if len(message) <= 40 else message[:40]
|
|
1035
|
-
save_to = cls.cache_dir / sanitize_filename(filename)
|
|
1036
|
-
save_to = save_to.as_posix()
|
|
1037
|
-
|
|
1038
|
-
# Ensure cache_dir exists
|
|
1039
|
-
cls.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
1040
|
-
|
|
1041
|
-
if save_to:
|
|
1042
|
-
if not save_to.endswith("mp3"):
|
|
1043
|
-
save_to += ".mp3"
|
|
1044
|
-
|
|
1045
|
-
with open(save_to, "wb") as fh:
|
|
1046
|
-
for chunk in resp.iter_content(chunk_size=512):
|
|
1047
|
-
fh.write(chunk)
|
|
1048
|
-
else:
|
|
1049
|
-
return resp.content
|
|
1050
|
-
return save_to
|
|
1051
|
-
|
|
1052
|
-
@staticmethod
|
|
1053
|
-
def play(path_to_audio_file: Union[Path, str]) -> NoReturn:
|
|
1054
|
-
"""Play audio (.mp3) using playsound.
|
|
1055
|
-
"""
|
|
1056
|
-
if not Path(path_to_audio_file).is_file():
|
|
1057
|
-
raise FileNotFoundError(f"File does not exist - '{path_to_audio_file}'")
|
|
1058
|
-
playsound(path_to_audio_file)
|
|
1059
|
-
class ProxyManager:
|
|
1060
|
-
def __init__(self, refresh_interval=60):
|
|
1061
|
-
self.proxies: List[Tuple[str, float]] = [] # Store proxy and its latency
|
|
1062
|
-
self.last_refresh: float = 0
|
|
1063
|
-
self.refresh_interval = refresh_interval
|
|
1064
|
-
self.lock = threading.Lock() # Add a lock for thread safety
|
|
1065
|
-
# Start auto-refresh in a separate thread
|
|
1066
|
-
threading.Thread(target=self.auto_refresh_proxies, daemon=True).start()
|
|
1067
|
-
|
|
1068
|
-
def fetch_proxies(self, max_proxies=50) -> List[str]:
|
|
1069
|
-
try:
|
|
1070
|
-
url = "https://api.proxyscrape.com/v2/?request=displayproxies&protocol=http&timeout=10000&country=all&ssl=all&anonymity=all"
|
|
1071
|
-
response = requests.get(url)
|
|
1072
|
-
proxies = response.text.split('\r\n')[:max_proxies] # Extract up to max_proxies
|
|
1073
|
-
return [proxy for proxy in proxies if proxy]
|
|
1074
|
-
except requests.RequestException as e:
|
|
1075
|
-
print(f"Error fetching proxies: {e}")
|
|
1076
|
-
return []
|
|
1077
|
-
|
|
1078
|
-
def test_proxy(self, proxy: str) -> Tuple[str, float] | None:
|
|
1079
|
-
# Test both HTTP and HTTPS
|
|
1080
|
-
for protocol in ['http', 'https']:
|
|
1081
|
-
try:
|
|
1082
|
-
start_time = time.time()
|
|
1083
|
-
response = requests.get('http://httpbin.org/ip', proxies={protocol: f"{protocol}://{proxy}"}, timeout=5)
|
|
1084
|
-
if response.status_code == 200:
|
|
1085
|
-
end_time = time.time()
|
|
1086
|
-
return proxy, end_time - start_time
|
|
1087
|
-
except requests.RequestException:
|
|
1088
|
-
pass
|
|
1089
|
-
return None
|
|
1090
|
-
|
|
1091
|
-
def refresh_proxies(self) -> int:
|
|
1092
|
-
new_proxies = self.fetch_proxies()
|
|
1093
|
-
threads = []
|
|
1094
|
-
working_proxies = []
|
|
1095
|
-
|
|
1096
|
-
# Use threading for faster proxy testing
|
|
1097
|
-
for proxy in new_proxies:
|
|
1098
|
-
thread = threading.Thread(target=self.test_proxy_and_append, args=(proxy, working_proxies))
|
|
1099
|
-
threads.append(thread)
|
|
1100
|
-
thread.start()
|
|
1101
|
-
|
|
1102
|
-
# Wait for all threads to complete
|
|
1103
|
-
for thread in threads:
|
|
1104
|
-
thread.join()
|
|
1105
|
-
|
|
1106
|
-
with self.lock: # Acquire lock before updating proxies list
|
|
1107
|
-
self.proxies = working_proxies
|
|
1108
|
-
self.last_refresh = time.time()
|
|
1109
|
-
|
|
1110
|
-
# print(f"Refreshed proxies at {datetime.now()}. Total working proxies: {len(self.proxies)}")
|
|
1111
|
-
return len(self.proxies)
|
|
1112
|
-
|
|
1113
|
-
def test_proxy_and_append(self, proxy: str, working_proxies: list):
|
|
1114
|
-
result = self.test_proxy(proxy)
|
|
1115
|
-
if result:
|
|
1116
|
-
with self.lock: # Acquire lock before appending to shared list
|
|
1117
|
-
working_proxies.append(result) # Append the proxy and its latency
|
|
1118
|
-
|
|
1119
|
-
def auto_refresh_proxies(self):
|
|
1120
|
-
while True:
|
|
1121
|
-
time.sleep(self.refresh_interval)
|
|
1122
|
-
self.refresh_proxies()
|
|
1123
|
-
|
|
1124
|
-
def get_fastest_proxy(self) -> str | None:
|
|
1125
|
-
with self.lock: # Acquire lock before accessing proxies list
|
|
1126
|
-
if self.proxies:
|
|
1127
|
-
# Sort proxies by latency and return the fastest
|
|
1128
|
-
self.proxies.sort(key=lambda x: x[1]) # Sort by latency
|
|
1129
|
-
return self.proxies[0][0] # Return the fastest proxy
|
|
1130
|
-
return None
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import platform
|
|
4
|
+
import subprocess
|
|
5
|
+
import logging
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import appdirs
|
|
9
|
+
import datetime
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import click
|
|
13
|
+
from rich.markdown import Markdown
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
import g4f
|
|
16
|
+
from typing import List, Tuple, Union
|
|
17
|
+
from typing import NoReturn
|
|
18
|
+
import requests
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from playsound import playsound
|
|
21
|
+
from time import sleep as wait
|
|
22
|
+
import pathlib
|
|
23
|
+
import urllib.parse
|
|
24
|
+
appdir = appdirs.AppDirs("AIWEBS", "webscout")
|
|
25
|
+
|
|
26
|
+
default_path = appdir.user_cache_dir
|
|
27
|
+
|
|
28
|
+
if not os.path.exists(default_path):
|
|
29
|
+
os.makedirs(default_path)
|
|
30
|
+
webai = [
|
|
31
|
+
"leo",
|
|
32
|
+
"openai",
|
|
33
|
+
"opengpt",
|
|
34
|
+
"koboldai",
|
|
35
|
+
"gemini",
|
|
36
|
+
"phind",
|
|
37
|
+
"blackboxai",
|
|
38
|
+
"g4fauto",
|
|
39
|
+
"perplexity",
|
|
40
|
+
"groq",
|
|
41
|
+
"reka",
|
|
42
|
+
"cohere",
|
|
43
|
+
"yepchat",
|
|
44
|
+
"you",
|
|
45
|
+
"xjai",
|
|
46
|
+
"thinkany",
|
|
47
|
+
"berlin4h",
|
|
48
|
+
"chatgptuk",
|
|
49
|
+
"auto",
|
|
50
|
+
"poe",
|
|
51
|
+
"basedgpt",
|
|
52
|
+
"deepseek",
|
|
53
|
+
"deepinfra",
|
|
54
|
+
"vtlchat",
|
|
55
|
+
"geminiflash",
|
|
56
|
+
"geminipro",
|
|
57
|
+
"ollama",
|
|
58
|
+
"andi",
|
|
59
|
+
"llama3"
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
gpt4free_providers = [
|
|
63
|
+
provider.__name__ for provider in g4f.Provider.__providers__ # if provider.working
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
available_providers = webai + gpt4free_providers
|
|
67
|
+
def sanitize_stream(
|
|
68
|
+
chunk: str, intro_value: str = "data:", to_json: bool = True
|
|
69
|
+
) -> str | dict:
|
|
70
|
+
"""Remove streaming flags
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
chunk (str): Streamig chunk.
|
|
74
|
+
intro_value (str, optional): streaming flag. Defaults to "data:".
|
|
75
|
+
to_json (bool, optional). Return chunk as dictionary. Defaults to True.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
str: Sanitized streaming value.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
if chunk.startswith(intro_value):
|
|
82
|
+
chunk = chunk[len(intro_value) :]
|
|
83
|
+
|
|
84
|
+
return json.loads(chunk) if to_json else chunk
|
|
85
|
+
def run_system_command(
|
|
86
|
+
command: str,
|
|
87
|
+
exit_on_error: bool = True,
|
|
88
|
+
stdout_error: bool = True,
|
|
89
|
+
help: str = None,
|
|
90
|
+
):
|
|
91
|
+
"""Run commands against system
|
|
92
|
+
Args:
|
|
93
|
+
command (str): shell command
|
|
94
|
+
exit_on_error (bool, optional): Exit on error. Defaults to True.
|
|
95
|
+
stdout_error (bool, optional): Print out the error. Defaults to True
|
|
96
|
+
help (str, optional): Help info incase of exception. Defaults to None.
|
|
97
|
+
Returns:
|
|
98
|
+
tuple : (is_successfull, object[Exception|Subprocess.run])
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
# Run the command and capture the output
|
|
102
|
+
result = subprocess.run(
|
|
103
|
+
command,
|
|
104
|
+
shell=True,
|
|
105
|
+
check=True,
|
|
106
|
+
text=True,
|
|
107
|
+
stdout=subprocess.PIPE,
|
|
108
|
+
stderr=subprocess.PIPE,
|
|
109
|
+
)
|
|
110
|
+
return (True, result)
|
|
111
|
+
except subprocess.CalledProcessError as e:
|
|
112
|
+
# Handle error if the command returns a non-zero exit code
|
|
113
|
+
if stdout_error:
|
|
114
|
+
click.secho(f"Error Occurred: while running '{command}'", fg="yellow")
|
|
115
|
+
click.secho(e.stderr, fg="red")
|
|
116
|
+
if help is not None:
|
|
117
|
+
click.secho(help, fg="cyan")
|
|
118
|
+
sys.exit(e.returncode) if exit_on_error else None
|
|
119
|
+
return (False, e)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
from .conversation import Conversation
|
|
123
|
+
|
|
124
|
+
from .optimizers import Optimizers
|
|
125
|
+
|
|
126
|
+
from .Extra.autocoder import AutoCoder
|
|
127
|
+
|
|
128
|
+
from .prompt_manager import AwesomePrompts
|
|
129
|
+
|
|
130
|
+
class Updates:
|
|
131
|
+
"""Webscout latest release info"""
|
|
132
|
+
|
|
133
|
+
url = "https://api.github.com/repos/OE-LUCIFER/Webscout/releases/latest"
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def latest_version(self):
|
|
137
|
+
return self.latest(version=True)
|
|
138
|
+
|
|
139
|
+
def executable(self, system: str = platform.system()) -> str:
|
|
140
|
+
"""Url pointing to executable for particular system
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
system (str, optional): system name. Defaults to platform.system().
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
str: url
|
|
147
|
+
"""
|
|
148
|
+
for entry in self.latest()["assets"]:
|
|
149
|
+
if entry.get("target") == system:
|
|
150
|
+
return entry.get("url")
|
|
151
|
+
|
|
152
|
+
def latest(self, whole: bool = False, version: bool = False) -> dict:
|
|
153
|
+
"""Check Webscout latest version info
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
whole (bool, optional): Return whole json response. Defaults to False.
|
|
157
|
+
version (bool, optional): return version only. Defaults to False.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
bool|dict: version str or whole dict info
|
|
161
|
+
"""
|
|
162
|
+
import requests
|
|
163
|
+
|
|
164
|
+
data = requests.get(self.url).json()
|
|
165
|
+
if whole:
|
|
166
|
+
return data
|
|
167
|
+
|
|
168
|
+
elif version:
|
|
169
|
+
return data.get("tag_name")
|
|
170
|
+
|
|
171
|
+
else:
|
|
172
|
+
sorted = dict(
|
|
173
|
+
tag_name=data.get("tag_name"),
|
|
174
|
+
tarball_url=data.get("tarball_url"),
|
|
175
|
+
zipball_url=data.get("zipball_url"),
|
|
176
|
+
html_url=data.get("html_url"),
|
|
177
|
+
body=data.get("body"),
|
|
178
|
+
)
|
|
179
|
+
whole_assets = []
|
|
180
|
+
for entry in data.get("assets"):
|
|
181
|
+
url = entry.get("browser_download_url")
|
|
182
|
+
assets = dict(url=url, size=entry.get("size"))
|
|
183
|
+
if ".deb" in url:
|
|
184
|
+
assets["target"] = "Debian"
|
|
185
|
+
elif ".exe" in url:
|
|
186
|
+
assets["target"] = "Windows"
|
|
187
|
+
elif "macos" in url:
|
|
188
|
+
assets["target"] = "Mac"
|
|
189
|
+
elif "linux" in url:
|
|
190
|
+
assets["target"] = "Linux"
|
|
191
|
+
|
|
192
|
+
whole_assets.append(assets)
|
|
193
|
+
sorted["assets"] = whole_assets
|
|
194
|
+
|
|
195
|
+
return sorted
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class Audio:
|
|
199
|
+
# Request headers
|
|
200
|
+
headers: dict[str, str] = {
|
|
201
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
|
|
202
|
+
}
|
|
203
|
+
cache_dir = pathlib.Path("./audio_cache")
|
|
204
|
+
all_voices: list[str] = [
|
|
205
|
+
"Filiz",
|
|
206
|
+
"Astrid",
|
|
207
|
+
"Tatyana",
|
|
208
|
+
"Maxim",
|
|
209
|
+
"Carmen",
|
|
210
|
+
"Ines",
|
|
211
|
+
"Cristiano",
|
|
212
|
+
"Vitoria",
|
|
213
|
+
"Ricardo",
|
|
214
|
+
"Maja",
|
|
215
|
+
"Jan",
|
|
216
|
+
"Jacek",
|
|
217
|
+
"Ewa",
|
|
218
|
+
"Ruben",
|
|
219
|
+
"Lotte",
|
|
220
|
+
"Liv",
|
|
221
|
+
"Seoyeon",
|
|
222
|
+
"Takumi",
|
|
223
|
+
"Mizuki",
|
|
224
|
+
"Giorgio",
|
|
225
|
+
"Carla",
|
|
226
|
+
"Bianca",
|
|
227
|
+
"Karl",
|
|
228
|
+
"Dora",
|
|
229
|
+
"Mathieu",
|
|
230
|
+
"Celine",
|
|
231
|
+
"Chantal",
|
|
232
|
+
"Penelope",
|
|
233
|
+
"Miguel",
|
|
234
|
+
"Mia",
|
|
235
|
+
"Enrique",
|
|
236
|
+
"Conchita",
|
|
237
|
+
"Geraint",
|
|
238
|
+
"Salli",
|
|
239
|
+
"Matthew",
|
|
240
|
+
"Kimberly",
|
|
241
|
+
"Kendra",
|
|
242
|
+
"Justin",
|
|
243
|
+
"Joey",
|
|
244
|
+
"Joanna",
|
|
245
|
+
"Ivy",
|
|
246
|
+
"Raveena",
|
|
247
|
+
"Aditi",
|
|
248
|
+
"Emma",
|
|
249
|
+
"Brian",
|
|
250
|
+
"Amy",
|
|
251
|
+
"Russell",
|
|
252
|
+
"Nicole",
|
|
253
|
+
"Vicki",
|
|
254
|
+
"Marlene",
|
|
255
|
+
"Hans",
|
|
256
|
+
"Naja",
|
|
257
|
+
"Mads",
|
|
258
|
+
"Gwyneth",
|
|
259
|
+
"Zhiyu",
|
|
260
|
+
"es-ES-Standard-A",
|
|
261
|
+
"it-IT-Standard-A",
|
|
262
|
+
"it-IT-Wavenet-A",
|
|
263
|
+
"ja-JP-Standard-A",
|
|
264
|
+
"ja-JP-Wavenet-A",
|
|
265
|
+
"ko-KR-Standard-A",
|
|
266
|
+
"ko-KR-Wavenet-A",
|
|
267
|
+
"pt-BR-Standard-A",
|
|
268
|
+
"tr-TR-Standard-A",
|
|
269
|
+
"sv-SE-Standard-A",
|
|
270
|
+
"nl-NL-Standard-A",
|
|
271
|
+
"nl-NL-Wavenet-A",
|
|
272
|
+
"en-US-Wavenet-A",
|
|
273
|
+
"en-US-Wavenet-B",
|
|
274
|
+
"en-US-Wavenet-C",
|
|
275
|
+
"en-US-Wavenet-D",
|
|
276
|
+
"en-US-Wavenet-E",
|
|
277
|
+
"en-US-Wavenet-F",
|
|
278
|
+
"en-GB-Standard-A",
|
|
279
|
+
"en-GB-Standard-B",
|
|
280
|
+
"en-GB-Standard-C",
|
|
281
|
+
"en-GB-Standard-D",
|
|
282
|
+
"en-GB-Wavenet-A",
|
|
283
|
+
"en-GB-Wavenet-B",
|
|
284
|
+
"en-GB-Wavenet-C",
|
|
285
|
+
"en-GB-Wavenet-D",
|
|
286
|
+
"en-US-Standard-B",
|
|
287
|
+
"en-US-Standard-C",
|
|
288
|
+
"en-US-Standard-D",
|
|
289
|
+
"en-US-Standard-E",
|
|
290
|
+
"de-DE-Standard-A",
|
|
291
|
+
"de-DE-Standard-B",
|
|
292
|
+
"de-DE-Wavenet-A",
|
|
293
|
+
"de-DE-Wavenet-B",
|
|
294
|
+
"de-DE-Wavenet-C",
|
|
295
|
+
"de-DE-Wavenet-D",
|
|
296
|
+
"en-AU-Standard-A",
|
|
297
|
+
"en-AU-Standard-B",
|
|
298
|
+
"en-AU-Wavenet-A",
|
|
299
|
+
"en-AU-Wavenet-B",
|
|
300
|
+
"en-AU-Wavenet-C",
|
|
301
|
+
"en-AU-Wavenet-D",
|
|
302
|
+
"en-AU-Standard-C",
|
|
303
|
+
"en-AU-Standard-D",
|
|
304
|
+
"fr-CA-Standard-A",
|
|
305
|
+
"fr-CA-Standard-B",
|
|
306
|
+
"fr-CA-Standard-C",
|
|
307
|
+
"fr-CA-Standard-D",
|
|
308
|
+
"fr-FR-Standard-C",
|
|
309
|
+
"fr-FR-Standard-D",
|
|
310
|
+
"fr-FR-Wavenet-A",
|
|
311
|
+
"fr-FR-Wavenet-B",
|
|
312
|
+
"fr-FR-Wavenet-C",
|
|
313
|
+
"fr-FR-Wavenet-D",
|
|
314
|
+
"da-DK-Wavenet-A",
|
|
315
|
+
"pl-PL-Wavenet-A",
|
|
316
|
+
"pl-PL-Wavenet-B",
|
|
317
|
+
"pl-PL-Wavenet-C",
|
|
318
|
+
"pl-PL-Wavenet-D",
|
|
319
|
+
"pt-PT-Wavenet-A",
|
|
320
|
+
"pt-PT-Wavenet-B",
|
|
321
|
+
"pt-PT-Wavenet-C",
|
|
322
|
+
"pt-PT-Wavenet-D",
|
|
323
|
+
"ru-RU-Wavenet-A",
|
|
324
|
+
"ru-RU-Wavenet-B",
|
|
325
|
+
"ru-RU-Wavenet-C",
|
|
326
|
+
"ru-RU-Wavenet-D",
|
|
327
|
+
"sk-SK-Wavenet-A",
|
|
328
|
+
"tr-TR-Wavenet-A",
|
|
329
|
+
"tr-TR-Wavenet-B",
|
|
330
|
+
"tr-TR-Wavenet-C",
|
|
331
|
+
"tr-TR-Wavenet-D",
|
|
332
|
+
"tr-TR-Wavenet-E",
|
|
333
|
+
"uk-UA-Wavenet-A",
|
|
334
|
+
"ar-XA-Wavenet-A",
|
|
335
|
+
"ar-XA-Wavenet-B",
|
|
336
|
+
"ar-XA-Wavenet-C",
|
|
337
|
+
"cs-CZ-Wavenet-A",
|
|
338
|
+
"nl-NL-Wavenet-B",
|
|
339
|
+
"nl-NL-Wavenet-C",
|
|
340
|
+
"nl-NL-Wavenet-D",
|
|
341
|
+
"nl-NL-Wavenet-E",
|
|
342
|
+
"en-IN-Wavenet-A",
|
|
343
|
+
"en-IN-Wavenet-B",
|
|
344
|
+
"en-IN-Wavenet-C",
|
|
345
|
+
"fil-PH-Wavenet-A",
|
|
346
|
+
"fi-FI-Wavenet-A",
|
|
347
|
+
"el-GR-Wavenet-A",
|
|
348
|
+
"hi-IN-Wavenet-A",
|
|
349
|
+
"hi-IN-Wavenet-B",
|
|
350
|
+
"hi-IN-Wavenet-C",
|
|
351
|
+
"hu-HU-Wavenet-A",
|
|
352
|
+
"id-ID-Wavenet-A",
|
|
353
|
+
"id-ID-Wavenet-B",
|
|
354
|
+
"id-ID-Wavenet-C",
|
|
355
|
+
"it-IT-Wavenet-B",
|
|
356
|
+
"it-IT-Wavenet-C",
|
|
357
|
+
"it-IT-Wavenet-D",
|
|
358
|
+
"ja-JP-Wavenet-B",
|
|
359
|
+
"ja-JP-Wavenet-C",
|
|
360
|
+
"ja-JP-Wavenet-D",
|
|
361
|
+
"cmn-CN-Wavenet-A",
|
|
362
|
+
"cmn-CN-Wavenet-B",
|
|
363
|
+
"cmn-CN-Wavenet-C",
|
|
364
|
+
"cmn-CN-Wavenet-D",
|
|
365
|
+
"nb-no-Wavenet-E",
|
|
366
|
+
"nb-no-Wavenet-A",
|
|
367
|
+
"nb-no-Wavenet-B",
|
|
368
|
+
"nb-no-Wavenet-C",
|
|
369
|
+
"nb-no-Wavenet-D",
|
|
370
|
+
"vi-VN-Wavenet-A",
|
|
371
|
+
"vi-VN-Wavenet-B",
|
|
372
|
+
"vi-VN-Wavenet-C",
|
|
373
|
+
"vi-VN-Wavenet-D",
|
|
374
|
+
"sr-rs-Standard-A",
|
|
375
|
+
"lv-lv-Standard-A",
|
|
376
|
+
"is-is-Standard-A",
|
|
377
|
+
"bg-bg-Standard-A",
|
|
378
|
+
"af-ZA-Standard-A",
|
|
379
|
+
"Tracy",
|
|
380
|
+
"Danny",
|
|
381
|
+
"Huihui",
|
|
382
|
+
"Yaoyao",
|
|
383
|
+
"Kangkang",
|
|
384
|
+
"HanHan",
|
|
385
|
+
"Zhiwei",
|
|
386
|
+
"Asaf",
|
|
387
|
+
"An",
|
|
388
|
+
"Stefanos",
|
|
389
|
+
"Filip",
|
|
390
|
+
"Ivan",
|
|
391
|
+
"Heidi",
|
|
392
|
+
"Herena",
|
|
393
|
+
"Kalpana",
|
|
394
|
+
"Hemant",
|
|
395
|
+
"Matej",
|
|
396
|
+
"Andika",
|
|
397
|
+
"Rizwan",
|
|
398
|
+
"Lado",
|
|
399
|
+
"Valluvar",
|
|
400
|
+
"Linda",
|
|
401
|
+
"Heather",
|
|
402
|
+
"Sean",
|
|
403
|
+
"Michael",
|
|
404
|
+
"Karsten",
|
|
405
|
+
"Guillaume",
|
|
406
|
+
"Pattara",
|
|
407
|
+
"Jakub",
|
|
408
|
+
"Szabolcs",
|
|
409
|
+
"Hoda",
|
|
410
|
+
"Naayf",
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
@classmethod
|
|
414
|
+
def text_to_audio(
|
|
415
|
+
cls,
|
|
416
|
+
message: str,
|
|
417
|
+
voice: str = "Brian",
|
|
418
|
+
save_to: Union[Path, str] = None,
|
|
419
|
+
auto: bool = True,
|
|
420
|
+
) -> Union[str, bytes]:
|
|
421
|
+
"""
|
|
422
|
+
Text to speech using StreamElements API
|
|
423
|
+
|
|
424
|
+
Parameters:
|
|
425
|
+
message (str): The text to convert to speech
|
|
426
|
+
voice (str, optional): The voice to use for speech synthesis. Defaults to "Brian".
|
|
427
|
+
save_to (bool, optional): Path to save the audio file. Defaults to None.
|
|
428
|
+
auto (bool, optional): Generate filename based on `message` and save to `cls.cache_dir`. Defaults to False.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
result (Union[str, bytes]): Path to saved contents or audio content.
|
|
432
|
+
"""
|
|
433
|
+
assert (
|
|
434
|
+
voice in cls.all_voices
|
|
435
|
+
), f"Voice '{voice}' not one of [{', '.join(cls.all_voices)}]"
|
|
436
|
+
# Base URL for provider API
|
|
437
|
+
url: str = (
|
|
438
|
+
f"https://api.streamelements.com/kappa/v2/speech?voice={voice}&text={{{urllib.parse.quote(message)}}}"
|
|
439
|
+
)
|
|
440
|
+
resp = requests.get(url=url, headers=cls.headers, stream=True)
|
|
441
|
+
if not resp.ok:
|
|
442
|
+
raise Exception(
|
|
443
|
+
f"Failed to perform the operation - ({resp.status_code}, {resp.reason}) - {resp.text}"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
def sanitize_filename(path):
|
|
447
|
+
trash = [
|
|
448
|
+
"\\",
|
|
449
|
+
"/",
|
|
450
|
+
":",
|
|
451
|
+
"*",
|
|
452
|
+
"?",
|
|
453
|
+
'"',
|
|
454
|
+
"<",
|
|
455
|
+
"|",
|
|
456
|
+
">",
|
|
457
|
+
]
|
|
458
|
+
for val in trash:
|
|
459
|
+
path = path.replace(val, "")
|
|
460
|
+
return path.strip()
|
|
461
|
+
|
|
462
|
+
if auto:
|
|
463
|
+
filename: str = message + "..." if len(message) <= 40 else message[:40]
|
|
464
|
+
save_to = cls.cache_dir / sanitize_filename(filename)
|
|
465
|
+
save_to = save_to.as_posix()
|
|
466
|
+
|
|
467
|
+
# Ensure cache_dir exists
|
|
468
|
+
cls.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
469
|
+
|
|
470
|
+
if save_to:
|
|
471
|
+
if not save_to.endswith("mp3"):
|
|
472
|
+
save_to += ".mp3"
|
|
473
|
+
|
|
474
|
+
with open(save_to, "wb") as fh:
|
|
475
|
+
for chunk in resp.iter_content(chunk_size=512):
|
|
476
|
+
fh.write(chunk)
|
|
477
|
+
else:
|
|
478
|
+
return resp.content
|
|
479
|
+
return save_to
|
|
480
|
+
|
|
481
|
+
@staticmethod
|
|
482
|
+
def play(path_to_audio_file: Union[Path, str]) -> NoReturn:
|
|
483
|
+
"""Play audio (.mp3) using playsound.
|
|
484
|
+
"""
|
|
485
|
+
if not Path(path_to_audio_file).is_file():
|
|
486
|
+
raise FileNotFoundError(f"File does not exist - '{path_to_audio_file}'")
|
|
487
|
+
playsound(path_to_audio_file)
|
|
488
|
+
#
|