webscout 8.2.7__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.

Files changed (323) hide show
  1. webscout/AIauto.py +1 -1
  2. webscout/AIutel.py +298 -249
  3. webscout/Extra/Act.md +309 -0
  4. webscout/Extra/GitToolkit/__init__.py +10 -0
  5. webscout/Extra/GitToolkit/gitapi/README.md +110 -0
  6. webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
  7. webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
  8. webscout/Extra/GitToolkit/gitapi/user.py +96 -0
  9. webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
  10. webscout/Extra/YTToolkit/README.md +375 -0
  11. webscout/Extra/YTToolkit/YTdownloader.py +957 -0
  12. webscout/Extra/YTToolkit/__init__.py +3 -0
  13. webscout/Extra/YTToolkit/transcriber.py +476 -0
  14. webscout/Extra/YTToolkit/ytapi/README.md +44 -0
  15. webscout/Extra/YTToolkit/ytapi/__init__.py +6 -0
  16. webscout/Extra/YTToolkit/ytapi/channel.py +307 -0
  17. webscout/Extra/YTToolkit/ytapi/errors.py +13 -0
  18. webscout/Extra/YTToolkit/ytapi/extras.py +118 -0
  19. webscout/Extra/YTToolkit/ytapi/https.py +88 -0
  20. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -0
  21. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -0
  22. webscout/Extra/YTToolkit/ytapi/pool.py +8 -0
  23. webscout/Extra/YTToolkit/ytapi/query.py +40 -0
  24. webscout/Extra/YTToolkit/ytapi/stream.py +63 -0
  25. webscout/Extra/YTToolkit/ytapi/utils.py +62 -0
  26. webscout/Extra/YTToolkit/ytapi/video.py +232 -0
  27. webscout/Extra/__init__.py +7 -0
  28. webscout/Extra/autocoder/__init__.py +9 -0
  29. webscout/Extra/autocoder/autocoder.py +1105 -0
  30. webscout/Extra/autocoder/autocoder_utiles.py +332 -0
  31. webscout/Extra/gguf.md +430 -0
  32. webscout/Extra/gguf.py +684 -0
  33. webscout/Extra/tempmail/README.md +488 -0
  34. webscout/Extra/tempmail/__init__.py +28 -0
  35. webscout/Extra/tempmail/async_utils.py +141 -0
  36. webscout/Extra/tempmail/base.py +161 -0
  37. webscout/Extra/tempmail/cli.py +187 -0
  38. webscout/Extra/tempmail/emailnator.py +84 -0
  39. webscout/Extra/tempmail/mail_tm.py +361 -0
  40. webscout/Extra/tempmail/temp_mail_io.py +292 -0
  41. webscout/Extra/weather.md +281 -0
  42. webscout/Extra/weather.py +194 -0
  43. webscout/Extra/weather_ascii.py +76 -0
  44. webscout/Litlogger/Readme.md +175 -0
  45. webscout/Litlogger/__init__.py +67 -0
  46. webscout/Litlogger/core/__init__.py +6 -0
  47. webscout/Litlogger/core/level.py +23 -0
  48. webscout/Litlogger/core/logger.py +165 -0
  49. webscout/Litlogger/handlers/__init__.py +12 -0
  50. webscout/Litlogger/handlers/console.py +33 -0
  51. webscout/Litlogger/handlers/file.py +143 -0
  52. webscout/Litlogger/handlers/network.py +173 -0
  53. webscout/Litlogger/styles/__init__.py +7 -0
  54. webscout/Litlogger/styles/colors.py +249 -0
  55. webscout/Litlogger/styles/formats.py +458 -0
  56. webscout/Litlogger/styles/text.py +87 -0
  57. webscout/Litlogger/utils/__init__.py +6 -0
  58. webscout/Litlogger/utils/detectors.py +153 -0
  59. webscout/Litlogger/utils/formatters.py +200 -0
  60. webscout/Provider/AI21.py +177 -0
  61. webscout/Provider/AISEARCH/DeepFind.py +254 -0
  62. webscout/Provider/AISEARCH/Perplexity.py +359 -0
  63. webscout/Provider/AISEARCH/README.md +279 -0
  64. webscout/Provider/AISEARCH/__init__.py +9 -0
  65. webscout/Provider/AISEARCH/felo_search.py +228 -0
  66. webscout/Provider/AISEARCH/genspark_search.py +350 -0
  67. webscout/Provider/AISEARCH/hika_search.py +198 -0
  68. webscout/Provider/AISEARCH/iask_search.py +436 -0
  69. webscout/Provider/AISEARCH/monica_search.py +246 -0
  70. webscout/Provider/AISEARCH/scira_search.py +324 -0
  71. webscout/Provider/AISEARCH/webpilotai_search.py +281 -0
  72. webscout/Provider/Aitopia.py +316 -0
  73. webscout/Provider/AllenAI.py +440 -0
  74. webscout/Provider/Andi.py +228 -0
  75. webscout/Provider/Blackboxai.py +673 -0
  76. webscout/Provider/ChatGPTClone.py +237 -0
  77. webscout/Provider/ChatGPTGratis.py +194 -0
  78. webscout/Provider/ChatSandbox.py +342 -0
  79. webscout/Provider/Cloudflare.py +324 -0
  80. webscout/Provider/Cohere.py +208 -0
  81. webscout/Provider/Deepinfra.py +340 -0
  82. webscout/Provider/ExaAI.py +261 -0
  83. webscout/Provider/ExaChat.py +358 -0
  84. webscout/Provider/Flowith.py +217 -0
  85. webscout/Provider/FreeGemini.py +250 -0
  86. webscout/Provider/Gemini.py +169 -0
  87. webscout/Provider/GithubChat.py +370 -0
  88. webscout/Provider/GizAI.py +295 -0
  89. webscout/Provider/Glider.py +225 -0
  90. webscout/Provider/Groq.py +801 -0
  91. webscout/Provider/HF_space/__init__.py +0 -0
  92. webscout/Provider/HF_space/qwen_qwen2.py +206 -0
  93. webscout/Provider/HeckAI.py +285 -0
  94. webscout/Provider/HuggingFaceChat.py +469 -0
  95. webscout/Provider/Hunyuan.py +283 -0
  96. webscout/Provider/Jadve.py +291 -0
  97. webscout/Provider/Koboldai.py +384 -0
  98. webscout/Provider/LambdaChat.py +411 -0
  99. webscout/Provider/Llama3.py +259 -0
  100. webscout/Provider/MCPCore.py +315 -0
  101. webscout/Provider/Marcus.py +198 -0
  102. webscout/Provider/Nemotron.py +218 -0
  103. webscout/Provider/Netwrck.py +270 -0
  104. webscout/Provider/OLLAMA.py +396 -0
  105. webscout/Provider/OPENAI/BLACKBOXAI.py +735 -0
  106. webscout/Provider/OPENAI/Cloudflare.py +378 -0
  107. webscout/Provider/OPENAI/FreeGemini.py +282 -0
  108. webscout/Provider/OPENAI/NEMOTRON.py +244 -0
  109. webscout/Provider/OPENAI/README.md +1253 -0
  110. webscout/Provider/OPENAI/__init__.py +36 -0
  111. webscout/Provider/OPENAI/ai4chat.py +293 -0
  112. webscout/Provider/OPENAI/api.py +810 -0
  113. webscout/Provider/OPENAI/base.py +249 -0
  114. webscout/Provider/OPENAI/c4ai.py +373 -0
  115. webscout/Provider/OPENAI/chatgpt.py +556 -0
  116. webscout/Provider/OPENAI/chatgptclone.py +488 -0
  117. webscout/Provider/OPENAI/chatsandbox.py +172 -0
  118. webscout/Provider/OPENAI/deepinfra.py +319 -0
  119. webscout/Provider/OPENAI/e2b.py +1356 -0
  120. webscout/Provider/OPENAI/exaai.py +411 -0
  121. webscout/Provider/OPENAI/exachat.py +443 -0
  122. webscout/Provider/OPENAI/flowith.py +162 -0
  123. webscout/Provider/OPENAI/freeaichat.py +359 -0
  124. webscout/Provider/OPENAI/glider.py +323 -0
  125. webscout/Provider/OPENAI/groq.py +361 -0
  126. webscout/Provider/OPENAI/heckai.py +307 -0
  127. webscout/Provider/OPENAI/llmchatco.py +335 -0
  128. webscout/Provider/OPENAI/mcpcore.py +383 -0
  129. webscout/Provider/OPENAI/multichat.py +376 -0
  130. webscout/Provider/OPENAI/netwrck.py +356 -0
  131. webscout/Provider/OPENAI/opkfc.py +496 -0
  132. webscout/Provider/OPENAI/scirachat.py +471 -0
  133. webscout/Provider/OPENAI/sonus.py +303 -0
  134. webscout/Provider/OPENAI/standardinput.py +433 -0
  135. webscout/Provider/OPENAI/textpollinations.py +339 -0
  136. webscout/Provider/OPENAI/toolbaz.py +413 -0
  137. webscout/Provider/OPENAI/typefully.py +355 -0
  138. webscout/Provider/OPENAI/typegpt.py +358 -0
  139. webscout/Provider/OPENAI/uncovrAI.py +462 -0
  140. webscout/Provider/OPENAI/utils.py +307 -0
  141. webscout/Provider/OPENAI/venice.py +425 -0
  142. webscout/Provider/OPENAI/wisecat.py +381 -0
  143. webscout/Provider/OPENAI/writecream.py +163 -0
  144. webscout/Provider/OPENAI/x0gpt.py +378 -0
  145. webscout/Provider/OPENAI/yep.py +356 -0
  146. webscout/Provider/OpenGPT.py +209 -0
  147. webscout/Provider/Openai.py +496 -0
  148. webscout/Provider/PI.py +429 -0
  149. webscout/Provider/Perplexitylabs.py +415 -0
  150. webscout/Provider/QwenLM.py +254 -0
  151. webscout/Provider/Reka.py +214 -0
  152. webscout/Provider/StandardInput.py +290 -0
  153. webscout/Provider/TTI/AiForce/README.md +159 -0
  154. webscout/Provider/TTI/AiForce/__init__.py +22 -0
  155. webscout/Provider/TTI/AiForce/async_aiforce.py +224 -0
  156. webscout/Provider/TTI/AiForce/sync_aiforce.py +245 -0
  157. webscout/Provider/TTI/FreeAIPlayground/README.md +99 -0
  158. webscout/Provider/TTI/FreeAIPlayground/__init__.py +9 -0
  159. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +181 -0
  160. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +180 -0
  161. webscout/Provider/TTI/ImgSys/README.md +174 -0
  162. webscout/Provider/TTI/ImgSys/__init__.py +23 -0
  163. webscout/Provider/TTI/ImgSys/async_imgsys.py +202 -0
  164. webscout/Provider/TTI/ImgSys/sync_imgsys.py +195 -0
  165. webscout/Provider/TTI/MagicStudio/README.md +101 -0
  166. webscout/Provider/TTI/MagicStudio/__init__.py +2 -0
  167. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +111 -0
  168. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +109 -0
  169. webscout/Provider/TTI/Nexra/README.md +155 -0
  170. webscout/Provider/TTI/Nexra/__init__.py +22 -0
  171. webscout/Provider/TTI/Nexra/async_nexra.py +286 -0
  172. webscout/Provider/TTI/Nexra/sync_nexra.py +258 -0
  173. webscout/Provider/TTI/PollinationsAI/README.md +146 -0
  174. webscout/Provider/TTI/PollinationsAI/__init__.py +23 -0
  175. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +311 -0
  176. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +265 -0
  177. webscout/Provider/TTI/README.md +128 -0
  178. webscout/Provider/TTI/__init__.py +12 -0
  179. webscout/Provider/TTI/aiarta/README.md +134 -0
  180. webscout/Provider/TTI/aiarta/__init__.py +2 -0
  181. webscout/Provider/TTI/aiarta/async_aiarta.py +482 -0
  182. webscout/Provider/TTI/aiarta/sync_aiarta.py +440 -0
  183. webscout/Provider/TTI/artbit/README.md +100 -0
  184. webscout/Provider/TTI/artbit/__init__.py +22 -0
  185. webscout/Provider/TTI/artbit/async_artbit.py +155 -0
  186. webscout/Provider/TTI/artbit/sync_artbit.py +148 -0
  187. webscout/Provider/TTI/fastflux/README.md +129 -0
  188. webscout/Provider/TTI/fastflux/__init__.py +22 -0
  189. webscout/Provider/TTI/fastflux/async_fastflux.py +261 -0
  190. webscout/Provider/TTI/fastflux/sync_fastflux.py +252 -0
  191. webscout/Provider/TTI/huggingface/README.md +114 -0
  192. webscout/Provider/TTI/huggingface/__init__.py +22 -0
  193. webscout/Provider/TTI/huggingface/async_huggingface.py +199 -0
  194. webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -0
  195. webscout/Provider/TTI/piclumen/README.md +161 -0
  196. webscout/Provider/TTI/piclumen/__init__.py +23 -0
  197. webscout/Provider/TTI/piclumen/async_piclumen.py +268 -0
  198. webscout/Provider/TTI/piclumen/sync_piclumen.py +233 -0
  199. webscout/Provider/TTI/pixelmuse/README.md +79 -0
  200. webscout/Provider/TTI/pixelmuse/__init__.py +4 -0
  201. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +249 -0
  202. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +182 -0
  203. webscout/Provider/TTI/talkai/README.md +139 -0
  204. webscout/Provider/TTI/talkai/__init__.py +4 -0
  205. webscout/Provider/TTI/talkai/async_talkai.py +229 -0
  206. webscout/Provider/TTI/talkai/sync_talkai.py +207 -0
  207. webscout/Provider/TTS/README.md +192 -0
  208. webscout/Provider/TTS/__init__.py +9 -0
  209. webscout/Provider/TTS/base.py +159 -0
  210. webscout/Provider/TTS/deepgram.py +156 -0
  211. webscout/Provider/TTS/elevenlabs.py +111 -0
  212. webscout/Provider/TTS/gesserit.py +128 -0
  213. webscout/Provider/TTS/murfai.py +113 -0
  214. webscout/Provider/TTS/parler.py +111 -0
  215. webscout/Provider/TTS/speechma.py +580 -0
  216. webscout/Provider/TTS/sthir.py +94 -0
  217. webscout/Provider/TTS/streamElements.py +333 -0
  218. webscout/Provider/TTS/utils.py +280 -0
  219. webscout/Provider/TeachAnything.py +229 -0
  220. webscout/Provider/TextPollinationsAI.py +308 -0
  221. webscout/Provider/TwoAI.py +280 -0
  222. webscout/Provider/TypliAI.py +305 -0
  223. webscout/Provider/UNFINISHED/ChatHub.py +209 -0
  224. webscout/Provider/UNFINISHED/Youchat.py +330 -0
  225. webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
  226. webscout/Provider/UNFINISHED/oivscode.py +351 -0
  227. webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
  228. webscout/Provider/Venice.py +258 -0
  229. webscout/Provider/VercelAI.py +253 -0
  230. webscout/Provider/WiseCat.py +233 -0
  231. webscout/Provider/WrDoChat.py +370 -0
  232. webscout/Provider/Writecream.py +246 -0
  233. webscout/Provider/WritingMate.py +269 -0
  234. webscout/Provider/__init__.py +172 -0
  235. webscout/Provider/ai4chat.py +149 -0
  236. webscout/Provider/akashgpt.py +335 -0
  237. webscout/Provider/asksteve.py +220 -0
  238. webscout/Provider/cerebras.py +290 -0
  239. webscout/Provider/chatglm.py +215 -0
  240. webscout/Provider/cleeai.py +213 -0
  241. webscout/Provider/copilot.py +425 -0
  242. webscout/Provider/elmo.py +283 -0
  243. webscout/Provider/freeaichat.py +285 -0
  244. webscout/Provider/geminiapi.py +208 -0
  245. webscout/Provider/granite.py +235 -0
  246. webscout/Provider/hermes.py +266 -0
  247. webscout/Provider/julius.py +223 -0
  248. webscout/Provider/koala.py +170 -0
  249. webscout/Provider/learnfastai.py +325 -0
  250. webscout/Provider/llama3mitril.py +215 -0
  251. webscout/Provider/llmchat.py +258 -0
  252. webscout/Provider/llmchatco.py +306 -0
  253. webscout/Provider/lmarena.py +198 -0
  254. webscout/Provider/meta.py +801 -0
  255. webscout/Provider/multichat.py +364 -0
  256. webscout/Provider/samurai.py +223 -0
  257. webscout/Provider/scira_chat.py +299 -0
  258. webscout/Provider/scnet.py +243 -0
  259. webscout/Provider/searchchat.py +292 -0
  260. webscout/Provider/sonus.py +258 -0
  261. webscout/Provider/talkai.py +194 -0
  262. webscout/Provider/toolbaz.py +353 -0
  263. webscout/Provider/turboseek.py +266 -0
  264. webscout/Provider/typefully.py +202 -0
  265. webscout/Provider/typegpt.py +289 -0
  266. webscout/Provider/uncovr.py +368 -0
  267. webscout/Provider/x0gpt.py +299 -0
  268. webscout/Provider/yep.py +389 -0
  269. webscout/__init__.py +4 -2
  270. webscout/cli.py +3 -28
  271. webscout/conversation.py +35 -35
  272. webscout/litagent/Readme.md +276 -0
  273. webscout/litagent/__init__.py +29 -0
  274. webscout/litagent/agent.py +455 -0
  275. webscout/litagent/constants.py +60 -0
  276. webscout/litprinter/__init__.py +59 -0
  277. webscout/scout/README.md +402 -0
  278. webscout/scout/__init__.py +8 -0
  279. webscout/scout/core/__init__.py +7 -0
  280. webscout/scout/core/crawler.py +140 -0
  281. webscout/scout/core/scout.py +568 -0
  282. webscout/scout/core/search_result.py +96 -0
  283. webscout/scout/core/text_analyzer.py +63 -0
  284. webscout/scout/core/text_utils.py +277 -0
  285. webscout/scout/core/web_analyzer.py +52 -0
  286. webscout/scout/element.py +460 -0
  287. webscout/scout/parsers/__init__.py +69 -0
  288. webscout/scout/parsers/html5lib_parser.py +172 -0
  289. webscout/scout/parsers/html_parser.py +236 -0
  290. webscout/scout/parsers/lxml_parser.py +178 -0
  291. webscout/scout/utils.py +37 -0
  292. webscout/swiftcli/Readme.md +323 -0
  293. webscout/swiftcli/__init__.py +95 -0
  294. webscout/swiftcli/core/__init__.py +7 -0
  295. webscout/swiftcli/core/cli.py +297 -0
  296. webscout/swiftcli/core/context.py +104 -0
  297. webscout/swiftcli/core/group.py +241 -0
  298. webscout/swiftcli/decorators/__init__.py +28 -0
  299. webscout/swiftcli/decorators/command.py +221 -0
  300. webscout/swiftcli/decorators/options.py +220 -0
  301. webscout/swiftcli/decorators/output.py +252 -0
  302. webscout/swiftcli/exceptions.py +21 -0
  303. webscout/swiftcli/plugins/__init__.py +9 -0
  304. webscout/swiftcli/plugins/base.py +135 -0
  305. webscout/swiftcli/plugins/manager.py +262 -0
  306. webscout/swiftcli/utils/__init__.py +59 -0
  307. webscout/swiftcli/utils/formatting.py +252 -0
  308. webscout/swiftcli/utils/parsing.py +267 -0
  309. webscout/version.py +1 -1
  310. webscout/webscout_search.py +2 -182
  311. webscout/webscout_search_async.py +1 -179
  312. webscout/zeroart/README.md +89 -0
  313. webscout/zeroart/__init__.py +135 -0
  314. webscout/zeroart/base.py +66 -0
  315. webscout/zeroart/effects.py +101 -0
  316. webscout/zeroart/fonts.py +1239 -0
  317. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/METADATA +115 -60
  318. webscout-8.2.8.dist-info/RECORD +334 -0
  319. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/WHEEL +1 -1
  320. webscout-8.2.7.dist-info/RECORD +0 -26
  321. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/entry_points.txt +0 -0
  322. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/licenses/LICENSE.md +0 -0
  323. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,267 @@
1
+ """Utility functions for parsing and validating command-line arguments."""
2
+
3
+ import os
4
+ import json
5
+ import yaml
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Union, Type
8
+
9
+ from ..exceptions import BadParameter, UsageError
10
+
11
+ def parse_args(args: List[str]) -> Dict[str, Any]:
12
+ """
13
+ Parse command line arguments into a dictionary.
14
+
15
+ Args:
16
+ args: List of command line arguments
17
+
18
+ Returns:
19
+ Dictionary of parsed arguments
20
+
21
+ Example:
22
+ >>> parse_args(['--name', 'test', '--flag', '-n', '42'])
23
+ {'name': 'test', 'flag': True, 'n': '42'}
24
+ """
25
+ parsed = {}
26
+ i = 0
27
+ while i < len(args):
28
+ arg = args[i]
29
+
30
+ # Handle flags/options
31
+ if arg.startswith('-'):
32
+ key = arg.lstrip('-').replace('-', '_')
33
+
34
+ # Check if next arg is a value or another flag
35
+ if i + 1 >= len(args) or args[i + 1].startswith('-'):
36
+ parsed[key] = True # Flag without value
37
+ else:
38
+ parsed[key] = args[i + 1]
39
+ i += 1
40
+ else:
41
+ # Positional argument
42
+ parsed[f'arg{len([k for k in parsed.keys() if k.startswith("arg")])}'] = arg
43
+
44
+ i += 1
45
+
46
+ return parsed
47
+
48
+ def validate_required(
49
+ params: Dict[str, Any],
50
+ required: List[str]
51
+ ) -> None:
52
+ """
53
+ Validate required parameters are present.
54
+
55
+ Args:
56
+ params: Parameter dictionary
57
+ required: List of required parameter names
58
+
59
+ Raises:
60
+ UsageError: If required parameter is missing
61
+ """
62
+ missing = [p for p in required if p not in params]
63
+ if missing:
64
+ raise UsageError(f"Missing required parameters: {', '.join(missing)}")
65
+
66
+ def convert_type(
67
+ value: str,
68
+ type_: Type,
69
+ param_name: str
70
+ ) -> Any:
71
+ """
72
+ Convert string value to specified type.
73
+
74
+ Args:
75
+ value: String value to convert
76
+ type_: Target type
77
+ param_name: Parameter name for error messages
78
+
79
+ Returns:
80
+ Converted value
81
+
82
+ Raises:
83
+ BadParameter: If conversion fails
84
+ """
85
+ try:
86
+ if type_ == bool:
87
+ return value.lower() in ('true', 't', 'yes', 'y', '1')
88
+ return type_(value)
89
+ except (ValueError, TypeError):
90
+ raise BadParameter(
91
+ f"Invalid value for {param_name}: {value} (expected {type_.__name__})"
92
+ )
93
+
94
+ def validate_choice(
95
+ value: Any,
96
+ choices: List[Any],
97
+ param_name: str,
98
+ case_sensitive: bool = True
99
+ ) -> None:
100
+ """
101
+ Validate value is one of allowed choices.
102
+
103
+ Args:
104
+ value: Value to validate
105
+ choices: List of allowed choices
106
+ param_name: Parameter name for error messages
107
+ case_sensitive: Whether to do case-sensitive comparison
108
+
109
+ Raises:
110
+ BadParameter: If value not in choices
111
+ """
112
+ if not case_sensitive and isinstance(value, str):
113
+ if value.lower() not in [str(c).lower() for c in choices]:
114
+ raise BadParameter(
115
+ f"Invalid choice for {param_name}: {value} "
116
+ f"(choose from {', '.join(str(c) for c in choices)})"
117
+ )
118
+ elif value not in choices:
119
+ raise BadParameter(
120
+ f"Invalid choice for {param_name}: {value} "
121
+ f"(choose from {', '.join(str(c) for c in choices)})"
122
+ )
123
+
124
+ def load_config_file(
125
+ path: Union[str, Path],
126
+ format: str = 'auto',
127
+ required: bool = True
128
+ ) -> Dict[str, Any]:
129
+ """
130
+ Load configuration from file.
131
+
132
+ Args:
133
+ path: Path to config file
134
+ format: File format (json, yaml, or auto)
135
+ required: Whether file is required
136
+
137
+ Returns:
138
+ Configuration dictionary
139
+
140
+ Raises:
141
+ UsageError: If required file not found or invalid format
142
+ """
143
+ path = Path(os.path.expanduser(path))
144
+
145
+ if not path.exists():
146
+ if required:
147
+ raise UsageError(f"Config file not found: {path}")
148
+ return {}
149
+
150
+ # Auto-detect format from extension
151
+ if format == 'auto':
152
+ format = path.suffix.lstrip('.').lower()
153
+ if format not in ('json', 'yaml', 'yml'):
154
+ raise UsageError(f"Unsupported config format: {format}")
155
+
156
+ try:
157
+ with open(path) as f:
158
+ if format == 'json':
159
+ return json.load(f)
160
+ elif format in ('yaml', 'yml'):
161
+ return yaml.safe_load(f)
162
+ else:
163
+ raise UsageError(f"Unsupported config format: {format}")
164
+ except Exception as e:
165
+ raise UsageError(f"Error loading config file: {str(e)}")
166
+
167
+ def parse_key_value(
168
+ value: str,
169
+ separator: str = '='
170
+ ) -> tuple:
171
+ """
172
+ Parse key-value string.
173
+
174
+ Args:
175
+ value: String in format "key=value"
176
+ separator: Key-value separator
177
+
178
+ Returns:
179
+ Tuple of (key, value)
180
+
181
+ Raises:
182
+ BadParameter: If string not in key=value format
183
+ """
184
+ try:
185
+ key, value = value.split(separator, 1)
186
+ return key.strip(), value.strip()
187
+ except ValueError:
188
+ raise BadParameter(
189
+ f"Invalid key-value pair: {value} (expected format: key{separator}value)"
190
+ )
191
+
192
+ def parse_list(
193
+ value: str,
194
+ separator: str = ','
195
+ ) -> List[str]:
196
+ """
197
+ Parse comma-separated list.
198
+
199
+ Args:
200
+ value: Comma-separated string
201
+ separator: List item separator
202
+
203
+ Returns:
204
+ List of strings
205
+ """
206
+ return [x.strip() for x in value.split(separator) if x.strip()]
207
+
208
+ def parse_dict(
209
+ value: str,
210
+ item_separator: str = ',',
211
+ key_value_separator: str = '='
212
+ ) -> Dict[str, str]:
213
+ """
214
+ Parse dictionary string.
215
+
216
+ Args:
217
+ value: String in format "key1=value1,key2=value2"
218
+ item_separator: Separator between items
219
+ key_value_separator: Separator between keys and values
220
+
221
+ Returns:
222
+ Dictionary of key-value pairs
223
+
224
+ Example:
225
+ >>> parse_dict("name=test,count=42")
226
+ {'name': 'test', 'count': '42'}
227
+ """
228
+ result = {}
229
+ if not value:
230
+ return result
231
+
232
+ items = parse_list(value, item_separator)
233
+ for item in items:
234
+ key, value = parse_key_value(item, key_value_separator)
235
+ result[key] = value
236
+
237
+ return result
238
+
239
+ def get_env_var(
240
+ name: str,
241
+ type_: Type = str,
242
+ required: bool = False,
243
+ default: Any = None
244
+ ) -> Any:
245
+ """
246
+ Get and validate environment variable.
247
+
248
+ Args:
249
+ name: Environment variable name
250
+ type_: Expected type
251
+ required: Whether variable is required
252
+ default: Default value if not set
253
+
254
+ Returns:
255
+ Environment variable value
256
+
257
+ Raises:
258
+ UsageError: If required variable not set
259
+ """
260
+ value = os.environ.get(name)
261
+
262
+ if value is None:
263
+ if required:
264
+ raise UsageError(f"Required environment variable not set: {name}")
265
+ return default
266
+
267
+ return convert_type(value, type_, name)
webscout/version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "8.2.7"
1
+ __version__ = "8.2.8"
2
2
  __prog__ = "webscout"
@@ -56,13 +56,7 @@ class WEBS:
56
56
  "firefox133", "firefox135",
57
57
  ) # fmt: skip
58
58
  _impersonates_os = ("android", "ios", "linux", "macos", "windows")
59
- _chat_models = {
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
@@ -103,11 +103,6 @@ class AsyncWEBS:
103
103
  self.sleep_timestamp = 0.0
104
104
 
105
105
  self._exception_event = asyncio.Event()
106
- self._chat_messages: List[Dict[str, str]] = []
107
- self._chat_tokens_count = 0
108
- self._chat_vqd: str = ""
109
- self._chat_vqd_hash: str = ""
110
- self._chat_xfe: str = ""
111
106
 
112
107
  async def __aenter__(self) -> AsyncWEBS:
113
108
  return self
@@ -189,179 +184,6 @@ class AsyncWEBS:
189
184
  resp_content = (await self._get_url("GET", "https://duckduckgo.com", params={"q": keywords})).content
190
185
  return _extract_vqd(resp_content, keywords)
191
186
 
192
- async def achat_yield(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> AsyncIterator[str]:
193
- """Initiates an async chat session with webscout AI.
194
-
195
- Args:
196
- keywords (str): The initial message or question to send to the AI.
197
- model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
198
- "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
199
- timeout (int): Timeout value for the HTTP client. Defaults to 30.
200
- max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
201
-
202
- Yields:
203
- str: Chunks of the response from the AI.
204
- """
205
- # Get Cloudflare Turnstile token
206
- async def get_turnstile_token():
207
- try:
208
- # Visit the DuckDuckGo chat page to get the Turnstile token
209
- resp_content = (await self._get_url(
210
- method="GET",
211
- url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
212
- )).content
213
-
214
- # Extract the Turnstile token if available
215
- if b'cf-turnstile-response' in resp_content:
216
- token = resp_content.split(b'cf-turnstile-response="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
217
- return token
218
- return ""
219
- except Exception:
220
- return ""
221
-
222
- # x-fe-version
223
- if not self._chat_xfe:
224
- resp_content = (await self._get_url(
225
- method="GET",
226
- url="https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=1",
227
- )).content
228
- try:
229
- xfe1 = resp_content.split(b'__DDG_BE_VERSION__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
230
- xfe2 = resp_content.split(b'__DDG_FE_CHAT_HASH__="', maxsplit=1)[1].split(b'"', maxsplit=1)[0].decode()
231
- self._chat_xfe = f"{xfe1}-{xfe2}"
232
- except Exception as ex:
233
- raise WebscoutE(
234
- f"achat_yield() Error to get _chat_xfe: {type(ex).__name__}: {ex}"
235
- ) from ex
236
- # vqd
237
- if not self._chat_vqd:
238
- resp = await self._get_url(
239
- method="GET", url="https://duckduckgo.com/duckchat/v1/status", headers={"x-vqd-accept": "1"}
240
- )
241
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
242
- self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
243
-
244
- self._chat_messages.append({"role": "user", "content": keywords})
245
- self._chat_tokens_count += max(len(keywords) // 4, 1) # approximate number of tokens
246
- if model not in self._chat_models:
247
- warnings.warn(f"{model=} is unavailable. Using 'gpt-4o-mini'", stacklevel=1)
248
- model = "gpt-4o-mini"
249
-
250
- # Get Cloudflare Turnstile token
251
- turnstile_token = await get_turnstile_token()
252
-
253
- json_data = {
254
- "model": self._chat_models[model],
255
- "messages": self._chat_messages,
256
- }
257
-
258
- # Add Turnstile token if available
259
- if turnstile_token:
260
- json_data["cf-turnstile-response"] = turnstile_token
261
-
262
- # Enhanced headers to better mimic a real browser
263
- chat_headers = {
264
- "x-fe-version": self._chat_xfe,
265
- "x-vqd-4": self._chat_vqd,
266
- "x-vqd-hash-1": "",
267
- "Accept": "text/event-stream",
268
- "Accept-Language": "en-US,en;q=0.9",
269
- "Cache-Control": "no-cache",
270
- "Content-Type": "application/json",
271
- "DNT": "1",
272
- "Origin": "https://duckduckgo.com",
273
- "Referer": "https://duckduckgo.com/",
274
- "Sec-Fetch-Dest": "empty",
275
- "Sec-Fetch-Mode": "cors",
276
- "Sec-Fetch-Site": "same-origin",
277
- "User-Agent": self.client.headers.get("User-Agent", "")
278
- }
279
-
280
- # Retry logic for rate limited requests
281
- retry_count = 0
282
- while retry_count <= max_retries:
283
- try:
284
- resp = await self._get_url(
285
- method="POST",
286
- url="https://duckduckgo.com/duckchat/v1/chat",
287
- headers=chat_headers,
288
- json=json_data,
289
- timeout=timeout,
290
- )
291
-
292
- self._chat_vqd = resp.headers.get("x-vqd-4", "")
293
- self._chat_vqd_hash = resp.headers.get("x-vqd-hash-1", "")
294
- chunks = []
295
-
296
- # curl_cffi uses aiter_content instead of aiter_bytes
297
- async for chunk in resp.aiter_content(chunk_size=1024):
298
- lines = chunk.split(b"data:")
299
- for line in lines:
300
- if line := line.strip():
301
- if line == b"[DONE]":
302
- break
303
- if line == b"[DONE][LIMIT_CONVERSATION]":
304
- raise ConversationLimitException("ERR_CONVERSATION_LIMIT")
305
- try:
306
- x = json_loads(line)
307
- if isinstance(x, dict):
308
- if x.get("action") == "error":
309
- err_message = x.get("type", "")
310
- if x.get("status") == 429:
311
- raise (
312
- ConversationLimitException(err_message)
313
- if err_message == "ERR_CONVERSATION_LIMIT"
314
- else RatelimitE(err_message)
315
- )
316
- raise WebscoutE(err_message)
317
- elif message := x.get("message"):
318
- chunks.append(message)
319
- yield message
320
- except Exception:
321
- # Skip invalid JSON data
322
- continue
323
-
324
- # If we get here, the request was successful
325
- result = "".join(chunks)
326
- self._chat_messages.append({"role": "assistant", "content": result})
327
- self._chat_tokens_count += len(result)
328
- return
329
-
330
- except RatelimitE as ex:
331
- retry_count += 1
332
- if retry_count > max_retries:
333
- raise WebscoutE(f"achat_yield() Rate limit exceeded after {max_retries} retries: {ex}") from ex
334
-
335
- # Get a fresh Turnstile token for the retry
336
- turnstile_token = await get_turnstile_token()
337
- if turnstile_token:
338
- json_data["cf-turnstile-response"] = turnstile_token
339
-
340
- # Exponential backoff
341
- sleep_time = 2 ** retry_count
342
- await asyncio.sleep(sleep_time)
343
-
344
- except Exception as ex:
345
- raise WebscoutE(f"achat_yield() {type(ex).__name__}: {ex}") from ex
346
-
347
- async def achat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30, max_retries: int = 3) -> str:
348
- """Initiates an async chat session with webscout AI.
349
-
350
- Args:
351
- keywords (str): The initial message or question to send to the AI.
352
- model (str): The model to use: "gpt-4o-mini", "llama-3.3-70b", "claude-3-haiku",
353
- "o3-mini", "mistral-small-3". Defaults to "gpt-4o-mini".
354
- timeout (int): Timeout value for the HTTP client. Defaults to 30.
355
- max_retries (int): Maximum number of retry attempts for rate limited requests. Defaults to 3.
356
-
357
- Returns:
358
- str: The response from the AI.
359
- """
360
- chunks = []
361
- async for chunk in self.achat_yield(keywords, model, timeout, max_retries):
362
- chunks.append(chunk)
363
- return "".join(chunks)
364
-
365
187
  async def atext(
366
188
  self,
367
189
  keywords: str,
@@ -829,4 +651,4 @@ class AsyncWEBS:
829
651
  TimeoutE: Inherits from WebscoutE, raised for API request timeouts.
830
652
  """
831
653
  # These methods are not implemented in the async version yet
832
- raise NotImplementedError("aweather method is not implemented yet")
654
+ raise NotImplementedError("aweather method is not implemented yet")