webscout 8.2.2__py3-none-any.whl → 2026.1.19__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.
Files changed (483) hide show
  1. webscout/AIauto.py +524 -143
  2. webscout/AIbase.py +247 -123
  3. webscout/AIutel.py +68 -132
  4. webscout/Bard.py +1072 -535
  5. webscout/Extra/GitToolkit/__init__.py +2 -2
  6. webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
  7. webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
  8. webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
  9. webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
  10. webscout/Extra/GitToolkit/gitapi/search.py +162 -0
  11. webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
  12. webscout/Extra/GitToolkit/gitapi/user.py +128 -96
  13. webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
  14. webscout/Extra/YTToolkit/README.md +443 -0
  15. webscout/Extra/YTToolkit/YTdownloader.py +953 -957
  16. webscout/Extra/YTToolkit/__init__.py +3 -3
  17. webscout/Extra/YTToolkit/transcriber.py +595 -476
  18. webscout/Extra/YTToolkit/ytapi/README.md +230 -0
  19. webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
  20. webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
  21. webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
  22. webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
  23. webscout/Extra/YTToolkit/ytapi/extras.py +178 -45
  24. webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
  25. webscout/Extra/YTToolkit/ytapi/https.py +89 -88
  26. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
  27. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
  28. webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
  29. webscout/Extra/YTToolkit/ytapi/query.py +143 -40
  30. webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
  31. webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
  32. webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
  33. webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
  34. webscout/Extra/YTToolkit/ytapi/video.py +189 -18
  35. webscout/Extra/__init__.py +2 -3
  36. webscout/Extra/gguf.py +1298 -682
  37. webscout/Extra/tempmail/README.md +488 -0
  38. webscout/Extra/tempmail/__init__.py +28 -28
  39. webscout/Extra/tempmail/async_utils.py +143 -141
  40. webscout/Extra/tempmail/base.py +172 -161
  41. webscout/Extra/tempmail/cli.py +191 -187
  42. webscout/Extra/tempmail/emailnator.py +88 -84
  43. webscout/Extra/tempmail/mail_tm.py +378 -361
  44. webscout/Extra/tempmail/temp_mail_io.py +304 -292
  45. webscout/Extra/weather.py +196 -194
  46. webscout/Extra/weather_ascii.py +17 -15
  47. webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
  48. webscout/Provider/AISEARCH/Perplexity.py +237 -304
  49. webscout/Provider/AISEARCH/README.md +106 -0
  50. webscout/Provider/AISEARCH/__init__.py +16 -10
  51. webscout/Provider/AISEARCH/brave_search.py +298 -0
  52. webscout/Provider/AISEARCH/iask_search.py +130 -209
  53. webscout/Provider/AISEARCH/monica_search.py +200 -246
  54. webscout/Provider/AISEARCH/webpilotai_search.py +242 -281
  55. webscout/Provider/Algion.py +413 -0
  56. webscout/Provider/Andi.py +74 -69
  57. webscout/Provider/Apriel.py +313 -0
  58. webscout/Provider/Ayle.py +323 -0
  59. webscout/Provider/ChatSandbox.py +329 -0
  60. webscout/Provider/ClaudeOnline.py +365 -0
  61. webscout/Provider/Cohere.py +232 -208
  62. webscout/Provider/DeepAI.py +367 -0
  63. webscout/Provider/Deepinfra.py +343 -173
  64. webscout/Provider/EssentialAI.py +217 -0
  65. webscout/Provider/ExaAI.py +274 -261
  66. webscout/Provider/Gemini.py +60 -54
  67. webscout/Provider/GithubChat.py +385 -367
  68. webscout/Provider/Gradient.py +286 -0
  69. webscout/Provider/Groq.py +556 -670
  70. webscout/Provider/HadadXYZ.py +323 -0
  71. webscout/Provider/HeckAI.py +392 -233
  72. webscout/Provider/HuggingFace.py +387 -0
  73. webscout/Provider/IBM.py +340 -0
  74. webscout/Provider/Jadve.py +317 -266
  75. webscout/Provider/K2Think.py +306 -0
  76. webscout/Provider/Koboldai.py +221 -381
  77. webscout/Provider/Netwrck.py +273 -228
  78. webscout/Provider/Nvidia.py +310 -0
  79. webscout/Provider/OPENAI/DeepAI.py +489 -0
  80. webscout/Provider/OPENAI/K2Think.py +423 -0
  81. webscout/Provider/OPENAI/PI.py +463 -0
  82. webscout/Provider/OPENAI/README.md +890 -0
  83. webscout/Provider/OPENAI/TogetherAI.py +405 -0
  84. webscout/Provider/OPENAI/TwoAI.py +255 -0
  85. webscout/Provider/OPENAI/__init__.py +148 -25
  86. webscout/Provider/OPENAI/ai4chat.py +348 -0
  87. webscout/Provider/OPENAI/akashgpt.py +436 -0
  88. webscout/Provider/OPENAI/algion.py +303 -0
  89. webscout/Provider/OPENAI/ayle.py +365 -0
  90. webscout/Provider/OPENAI/base.py +253 -46
  91. webscout/Provider/OPENAI/cerebras.py +296 -0
  92. webscout/Provider/OPENAI/chatgpt.py +514 -193
  93. webscout/Provider/OPENAI/chatsandbox.py +233 -0
  94. webscout/Provider/OPENAI/deepinfra.py +403 -272
  95. webscout/Provider/OPENAI/e2b.py +2370 -1350
  96. webscout/Provider/OPENAI/elmo.py +278 -0
  97. webscout/Provider/OPENAI/exaai.py +186 -138
  98. webscout/Provider/OPENAI/freeassist.py +446 -0
  99. webscout/Provider/OPENAI/gradient.py +448 -0
  100. webscout/Provider/OPENAI/groq.py +380 -0
  101. webscout/Provider/OPENAI/hadadxyz.py +292 -0
  102. webscout/Provider/OPENAI/heckai.py +100 -104
  103. webscout/Provider/OPENAI/huggingface.py +321 -0
  104. webscout/Provider/OPENAI/ibm.py +425 -0
  105. webscout/Provider/OPENAI/llmchat.py +253 -0
  106. webscout/Provider/OPENAI/llmchatco.py +378 -327
  107. webscout/Provider/OPENAI/meta.py +541 -0
  108. webscout/Provider/OPENAI/netwrck.py +110 -84
  109. webscout/Provider/OPENAI/nvidia.py +317 -0
  110. webscout/Provider/OPENAI/oivscode.py +348 -0
  111. webscout/Provider/OPENAI/openrouter.py +328 -0
  112. webscout/Provider/OPENAI/pydantic_imports.py +1 -0
  113. webscout/Provider/OPENAI/sambanova.py +397 -0
  114. webscout/Provider/OPENAI/sonus.py +126 -115
  115. webscout/Provider/OPENAI/textpollinations.py +218 -133
  116. webscout/Provider/OPENAI/toolbaz.py +136 -166
  117. webscout/Provider/OPENAI/typefully.py +419 -0
  118. webscout/Provider/OPENAI/typliai.py +279 -0
  119. webscout/Provider/OPENAI/utils.py +314 -211
  120. webscout/Provider/OPENAI/wisecat.py +103 -125
  121. webscout/Provider/OPENAI/writecream.py +185 -156
  122. webscout/Provider/OPENAI/x0gpt.py +227 -136
  123. webscout/Provider/OPENAI/zenmux.py +380 -0
  124. webscout/Provider/OpenRouter.py +386 -0
  125. webscout/Provider/Openai.py +337 -496
  126. webscout/Provider/PI.py +443 -344
  127. webscout/Provider/QwenLM.py +346 -254
  128. webscout/Provider/STT/__init__.py +28 -0
  129. webscout/Provider/STT/base.py +303 -0
  130. webscout/Provider/STT/elevenlabs.py +264 -0
  131. webscout/Provider/Sambanova.py +317 -0
  132. webscout/Provider/TTI/README.md +69 -0
  133. webscout/Provider/TTI/__init__.py +37 -12
  134. webscout/Provider/TTI/base.py +147 -0
  135. webscout/Provider/TTI/claudeonline.py +393 -0
  136. webscout/Provider/TTI/magicstudio.py +292 -0
  137. webscout/Provider/TTI/miragic.py +180 -0
  138. webscout/Provider/TTI/pollinations.py +331 -0
  139. webscout/Provider/TTI/together.py +334 -0
  140. webscout/Provider/TTI/utils.py +14 -0
  141. webscout/Provider/TTS/README.md +186 -0
  142. webscout/Provider/TTS/__init__.py +43 -7
  143. webscout/Provider/TTS/base.py +523 -0
  144. webscout/Provider/TTS/deepgram.py +286 -156
  145. webscout/Provider/TTS/elevenlabs.py +189 -111
  146. webscout/Provider/TTS/freetts.py +218 -0
  147. webscout/Provider/TTS/murfai.py +288 -113
  148. webscout/Provider/TTS/openai_fm.py +364 -0
  149. webscout/Provider/TTS/parler.py +203 -111
  150. webscout/Provider/TTS/qwen.py +334 -0
  151. webscout/Provider/TTS/sherpa.py +286 -0
  152. webscout/Provider/TTS/speechma.py +693 -180
  153. webscout/Provider/TTS/streamElements.py +275 -333
  154. webscout/Provider/TTS/utils.py +280 -280
  155. webscout/Provider/TextPollinationsAI.py +221 -121
  156. webscout/Provider/TogetherAI.py +450 -0
  157. webscout/Provider/TwoAI.py +309 -199
  158. webscout/Provider/TypliAI.py +311 -0
  159. webscout/Provider/UNFINISHED/ChatHub.py +219 -0
  160. webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +160 -145
  161. webscout/Provider/UNFINISHED/GizAI.py +300 -0
  162. webscout/Provider/UNFINISHED/Marcus.py +218 -0
  163. webscout/Provider/UNFINISHED/Qodo.py +481 -0
  164. webscout/Provider/UNFINISHED/XenAI.py +330 -0
  165. webscout/Provider/{Youchat.py → UNFINISHED/Youchat.py} +64 -47
  166. webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
  167. webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
  168. webscout/Provider/UNFINISHED/liner.py +342 -0
  169. webscout/Provider/UNFINISHED/liner_api_request.py +246 -0
  170. webscout/Provider/UNFINISHED/samurai.py +231 -0
  171. webscout/Provider/WiseCat.py +256 -196
  172. webscout/Provider/WrDoChat.py +390 -0
  173. webscout/Provider/__init__.py +115 -198
  174. webscout/Provider/ai4chat.py +181 -202
  175. webscout/Provider/akashgpt.py +330 -342
  176. webscout/Provider/cerebras.py +397 -242
  177. webscout/Provider/cleeai.py +236 -213
  178. webscout/Provider/elmo.py +291 -234
  179. webscout/Provider/geminiapi.py +343 -208
  180. webscout/Provider/julius.py +245 -223
  181. webscout/Provider/learnfastai.py +333 -266
  182. webscout/Provider/llama3mitril.py +230 -180
  183. webscout/Provider/llmchat.py +308 -213
  184. webscout/Provider/llmchatco.py +321 -311
  185. webscout/Provider/meta.py +996 -794
  186. webscout/Provider/oivscode.py +332 -0
  187. webscout/Provider/searchchat.py +316 -293
  188. webscout/Provider/sonus.py +264 -208
  189. webscout/Provider/toolbaz.py +359 -320
  190. webscout/Provider/turboseek.py +332 -219
  191. webscout/Provider/typefully.py +262 -280
  192. webscout/Provider/x0gpt.py +332 -256
  193. webscout/__init__.py +31 -38
  194. webscout/__main__.py +5 -5
  195. webscout/cli.py +585 -293
  196. webscout/client.py +1497 -0
  197. webscout/conversation.py +140 -565
  198. webscout/exceptions.py +383 -339
  199. webscout/litagent/__init__.py +29 -29
  200. webscout/litagent/agent.py +492 -455
  201. webscout/litagent/constants.py +60 -60
  202. webscout/models.py +505 -181
  203. webscout/optimizers.py +32 -378
  204. webscout/prompt_manager.py +376 -274
  205. webscout/sanitize.py +1514 -0
  206. webscout/scout/README.md +452 -0
  207. webscout/scout/__init__.py +8 -8
  208. webscout/scout/core/__init__.py +7 -7
  209. webscout/scout/core/crawler.py +330 -140
  210. webscout/scout/core/scout.py +800 -568
  211. webscout/scout/core/search_result.py +51 -96
  212. webscout/scout/core/text_analyzer.py +64 -63
  213. webscout/scout/core/text_utils.py +412 -277
  214. webscout/scout/core/web_analyzer.py +54 -52
  215. webscout/scout/element.py +872 -460
  216. webscout/scout/parsers/__init__.py +70 -69
  217. webscout/scout/parsers/html5lib_parser.py +182 -172
  218. webscout/scout/parsers/html_parser.py +238 -236
  219. webscout/scout/parsers/lxml_parser.py +203 -178
  220. webscout/scout/utils.py +38 -37
  221. webscout/search/__init__.py +47 -0
  222. webscout/search/base.py +201 -0
  223. webscout/search/bing_main.py +45 -0
  224. webscout/search/brave_main.py +92 -0
  225. webscout/search/duckduckgo_main.py +57 -0
  226. webscout/search/engines/__init__.py +127 -0
  227. webscout/search/engines/bing/__init__.py +15 -0
  228. webscout/search/engines/bing/base.py +35 -0
  229. webscout/search/engines/bing/images.py +114 -0
  230. webscout/search/engines/bing/news.py +96 -0
  231. webscout/search/engines/bing/suggestions.py +36 -0
  232. webscout/search/engines/bing/text.py +109 -0
  233. webscout/search/engines/brave/__init__.py +19 -0
  234. webscout/search/engines/brave/base.py +47 -0
  235. webscout/search/engines/brave/images.py +213 -0
  236. webscout/search/engines/brave/news.py +353 -0
  237. webscout/search/engines/brave/suggestions.py +318 -0
  238. webscout/search/engines/brave/text.py +167 -0
  239. webscout/search/engines/brave/videos.py +364 -0
  240. webscout/search/engines/duckduckgo/__init__.py +25 -0
  241. webscout/search/engines/duckduckgo/answers.py +80 -0
  242. webscout/search/engines/duckduckgo/base.py +189 -0
  243. webscout/search/engines/duckduckgo/images.py +100 -0
  244. webscout/search/engines/duckduckgo/maps.py +183 -0
  245. webscout/search/engines/duckduckgo/news.py +70 -0
  246. webscout/search/engines/duckduckgo/suggestions.py +22 -0
  247. webscout/search/engines/duckduckgo/text.py +221 -0
  248. webscout/search/engines/duckduckgo/translate.py +48 -0
  249. webscout/search/engines/duckduckgo/videos.py +80 -0
  250. webscout/search/engines/duckduckgo/weather.py +84 -0
  251. webscout/search/engines/mojeek.py +61 -0
  252. webscout/search/engines/wikipedia.py +77 -0
  253. webscout/search/engines/yahoo/__init__.py +41 -0
  254. webscout/search/engines/yahoo/answers.py +19 -0
  255. webscout/search/engines/yahoo/base.py +34 -0
  256. webscout/search/engines/yahoo/images.py +323 -0
  257. webscout/search/engines/yahoo/maps.py +19 -0
  258. webscout/search/engines/yahoo/news.py +258 -0
  259. webscout/search/engines/yahoo/suggestions.py +140 -0
  260. webscout/search/engines/yahoo/text.py +273 -0
  261. webscout/search/engines/yahoo/translate.py +19 -0
  262. webscout/search/engines/yahoo/videos.py +302 -0
  263. webscout/search/engines/yahoo/weather.py +220 -0
  264. webscout/search/engines/yandex.py +67 -0
  265. webscout/search/engines/yep/__init__.py +13 -0
  266. webscout/search/engines/yep/base.py +34 -0
  267. webscout/search/engines/yep/images.py +101 -0
  268. webscout/search/engines/yep/suggestions.py +38 -0
  269. webscout/search/engines/yep/text.py +99 -0
  270. webscout/search/http_client.py +172 -0
  271. webscout/search/results.py +141 -0
  272. webscout/search/yahoo_main.py +57 -0
  273. webscout/search/yep_main.py +48 -0
  274. webscout/server/__init__.py +48 -0
  275. webscout/server/config.py +78 -0
  276. webscout/server/exceptions.py +69 -0
  277. webscout/server/providers.py +286 -0
  278. webscout/server/request_models.py +131 -0
  279. webscout/server/request_processing.py +404 -0
  280. webscout/server/routes.py +642 -0
  281. webscout/server/server.py +351 -0
  282. webscout/server/ui_templates.py +1171 -0
  283. webscout/swiftcli/__init__.py +79 -809
  284. webscout/swiftcli/core/__init__.py +7 -0
  285. webscout/swiftcli/core/cli.py +574 -0
  286. webscout/swiftcli/core/context.py +98 -0
  287. webscout/swiftcli/core/group.py +268 -0
  288. webscout/swiftcli/decorators/__init__.py +28 -0
  289. webscout/swiftcli/decorators/command.py +243 -0
  290. webscout/swiftcli/decorators/options.py +247 -0
  291. webscout/swiftcli/decorators/output.py +392 -0
  292. webscout/swiftcli/exceptions.py +21 -0
  293. webscout/swiftcli/plugins/__init__.py +9 -0
  294. webscout/swiftcli/plugins/base.py +134 -0
  295. webscout/swiftcli/plugins/manager.py +269 -0
  296. webscout/swiftcli/utils/__init__.py +58 -0
  297. webscout/swiftcli/utils/formatting.py +251 -0
  298. webscout/swiftcli/utils/parsing.py +368 -0
  299. webscout/update_checker.py +280 -136
  300. webscout/utils.py +28 -14
  301. webscout/version.py +2 -1
  302. webscout/version.py.bak +3 -0
  303. webscout/zeroart/__init__.py +218 -55
  304. webscout/zeroart/base.py +70 -60
  305. webscout/zeroart/effects.py +155 -99
  306. webscout/zeroart/fonts.py +1799 -816
  307. webscout-2026.1.19.dist-info/METADATA +638 -0
  308. webscout-2026.1.19.dist-info/RECORD +312 -0
  309. {webscout-8.2.2.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
  310. webscout-2026.1.19.dist-info/entry_points.txt +4 -0
  311. webscout-2026.1.19.dist-info/top_level.txt +1 -0
  312. inferno/__init__.py +0 -6
  313. inferno/__main__.py +0 -9
  314. inferno/cli.py +0 -6
  315. webscout/DWEBS.py +0 -477
  316. webscout/Extra/autocoder/__init__.py +0 -9
  317. webscout/Extra/autocoder/autocoder.py +0 -849
  318. webscout/Extra/autocoder/autocoder_utiles.py +0 -332
  319. webscout/LLM.py +0 -442
  320. webscout/Litlogger/__init__.py +0 -67
  321. webscout/Litlogger/core/__init__.py +0 -6
  322. webscout/Litlogger/core/level.py +0 -23
  323. webscout/Litlogger/core/logger.py +0 -165
  324. webscout/Litlogger/handlers/__init__.py +0 -12
  325. webscout/Litlogger/handlers/console.py +0 -33
  326. webscout/Litlogger/handlers/file.py +0 -143
  327. webscout/Litlogger/handlers/network.py +0 -173
  328. webscout/Litlogger/styles/__init__.py +0 -7
  329. webscout/Litlogger/styles/colors.py +0 -249
  330. webscout/Litlogger/styles/formats.py +0 -458
  331. webscout/Litlogger/styles/text.py +0 -87
  332. webscout/Litlogger/utils/__init__.py +0 -6
  333. webscout/Litlogger/utils/detectors.py +0 -153
  334. webscout/Litlogger/utils/formatters.py +0 -200
  335. webscout/Local/__init__.py +0 -12
  336. webscout/Local/__main__.py +0 -9
  337. webscout/Local/api.py +0 -576
  338. webscout/Local/cli.py +0 -516
  339. webscout/Local/config.py +0 -75
  340. webscout/Local/llm.py +0 -287
  341. webscout/Local/model_manager.py +0 -253
  342. webscout/Local/server.py +0 -721
  343. webscout/Local/utils.py +0 -93
  344. webscout/Provider/AI21.py +0 -177
  345. webscout/Provider/AISEARCH/DeepFind.py +0 -250
  346. webscout/Provider/AISEARCH/ISou.py +0 -256
  347. webscout/Provider/AISEARCH/felo_search.py +0 -228
  348. webscout/Provider/AISEARCH/genspark_search.py +0 -208
  349. webscout/Provider/AISEARCH/hika_search.py +0 -194
  350. webscout/Provider/AISEARCH/scira_search.py +0 -324
  351. webscout/Provider/Aitopia.py +0 -292
  352. webscout/Provider/AllenAI.py +0 -413
  353. webscout/Provider/Blackboxai.py +0 -229
  354. webscout/Provider/C4ai.py +0 -432
  355. webscout/Provider/ChatGPTClone.py +0 -226
  356. webscout/Provider/ChatGPTES.py +0 -237
  357. webscout/Provider/ChatGPTGratis.py +0 -194
  358. webscout/Provider/Chatify.py +0 -175
  359. webscout/Provider/Cloudflare.py +0 -273
  360. webscout/Provider/DeepSeek.py +0 -196
  361. webscout/Provider/ElectronHub.py +0 -709
  362. webscout/Provider/ExaChat.py +0 -342
  363. webscout/Provider/Free2GPT.py +0 -241
  364. webscout/Provider/GPTWeb.py +0 -193
  365. webscout/Provider/Glider.py +0 -211
  366. webscout/Provider/HF_space/__init__.py +0 -0
  367. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  368. webscout/Provider/HuggingFaceChat.py +0 -462
  369. webscout/Provider/Hunyuan.py +0 -272
  370. webscout/Provider/LambdaChat.py +0 -392
  371. webscout/Provider/Llama.py +0 -200
  372. webscout/Provider/Llama3.py +0 -204
  373. webscout/Provider/Marcus.py +0 -148
  374. webscout/Provider/OLLAMA.py +0 -396
  375. webscout/Provider/OPENAI/c4ai.py +0 -367
  376. webscout/Provider/OPENAI/chatgptclone.py +0 -460
  377. webscout/Provider/OPENAI/exachat.py +0 -433
  378. webscout/Provider/OPENAI/freeaichat.py +0 -352
  379. webscout/Provider/OPENAI/opkfc.py +0 -488
  380. webscout/Provider/OPENAI/scirachat.py +0 -463
  381. webscout/Provider/OPENAI/standardinput.py +0 -425
  382. webscout/Provider/OPENAI/typegpt.py +0 -346
  383. webscout/Provider/OPENAI/uncovrAI.py +0 -455
  384. webscout/Provider/OPENAI/venice.py +0 -413
  385. webscout/Provider/OPENAI/yep.py +0 -327
  386. webscout/Provider/OpenGPT.py +0 -199
  387. webscout/Provider/Perplexitylabs.py +0 -415
  388. webscout/Provider/Phind.py +0 -535
  389. webscout/Provider/PizzaGPT.py +0 -198
  390. webscout/Provider/Reka.py +0 -214
  391. webscout/Provider/StandardInput.py +0 -278
  392. webscout/Provider/TTI/AiForce/__init__.py +0 -22
  393. webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
  394. webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
  395. webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
  396. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
  397. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
  398. webscout/Provider/TTI/ImgSys/__init__.py +0 -23
  399. webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
  400. webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
  401. webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
  402. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
  403. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
  404. webscout/Provider/TTI/Nexra/__init__.py +0 -22
  405. webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
  406. webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
  407. webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
  408. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
  409. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
  410. webscout/Provider/TTI/aiarta/__init__.py +0 -2
  411. webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
  412. webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
  413. webscout/Provider/TTI/artbit/__init__.py +0 -22
  414. webscout/Provider/TTI/artbit/async_artbit.py +0 -155
  415. webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
  416. webscout/Provider/TTI/fastflux/__init__.py +0 -22
  417. webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
  418. webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
  419. webscout/Provider/TTI/huggingface/__init__.py +0 -22
  420. webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
  421. webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
  422. webscout/Provider/TTI/piclumen/__init__.py +0 -23
  423. webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
  424. webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
  425. webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
  426. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
  427. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
  428. webscout/Provider/TTI/talkai/__init__.py +0 -4
  429. webscout/Provider/TTI/talkai/async_talkai.py +0 -229
  430. webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
  431. webscout/Provider/TTS/gesserit.py +0 -127
  432. webscout/Provider/TeachAnything.py +0 -187
  433. webscout/Provider/Venice.py +0 -219
  434. webscout/Provider/VercelAI.py +0 -234
  435. webscout/Provider/WebSim.py +0 -228
  436. webscout/Provider/Writecream.py +0 -211
  437. webscout/Provider/WritingMate.py +0 -197
  438. webscout/Provider/aimathgpt.py +0 -189
  439. webscout/Provider/askmyai.py +0 -158
  440. webscout/Provider/asksteve.py +0 -203
  441. webscout/Provider/bagoodex.py +0 -145
  442. webscout/Provider/chatglm.py +0 -205
  443. webscout/Provider/copilot.py +0 -428
  444. webscout/Provider/freeaichat.py +0 -271
  445. webscout/Provider/gaurish.py +0 -244
  446. webscout/Provider/geminiprorealtime.py +0 -160
  447. webscout/Provider/granite.py +0 -187
  448. webscout/Provider/hermes.py +0 -219
  449. webscout/Provider/koala.py +0 -268
  450. webscout/Provider/labyrinth.py +0 -340
  451. webscout/Provider/lepton.py +0 -194
  452. webscout/Provider/llamatutor.py +0 -192
  453. webscout/Provider/multichat.py +0 -325
  454. webscout/Provider/promptrefine.py +0 -193
  455. webscout/Provider/scira_chat.py +0 -277
  456. webscout/Provider/scnet.py +0 -187
  457. webscout/Provider/talkai.py +0 -194
  458. webscout/Provider/tutorai.py +0 -252
  459. webscout/Provider/typegpt.py +0 -232
  460. webscout/Provider/uncovr.py +0 -312
  461. webscout/Provider/yep.py +0 -376
  462. webscout/litprinter/__init__.py +0 -59
  463. webscout/scout/core.py +0 -881
  464. webscout/tempid.py +0 -128
  465. webscout/webscout_search.py +0 -1346
  466. webscout/webscout_search_async.py +0 -877
  467. webscout/yep_search.py +0 -297
  468. webscout-8.2.2.dist-info/METADATA +0 -734
  469. webscout-8.2.2.dist-info/RECORD +0 -309
  470. webscout-8.2.2.dist-info/entry_points.txt +0 -5
  471. webscout-8.2.2.dist-info/top_level.txt +0 -3
  472. webstoken/__init__.py +0 -30
  473. webstoken/classifier.py +0 -189
  474. webstoken/keywords.py +0 -216
  475. webstoken/language.py +0 -128
  476. webstoken/ner.py +0 -164
  477. webstoken/normalizer.py +0 -35
  478. webstoken/processor.py +0 -77
  479. webstoken/sentiment.py +0 -206
  480. webstoken/stemmer.py +0 -73
  481. webstoken/tagger.py +0 -60
  482. webstoken/tokenizer.py +0 -158
  483. {webscout-8.2.2.dist-info → webscout-2026.1.19.dist-info/licenses}/LICENSE.md +0 -0
@@ -0,0 +1,167 @@
1
+ """Brave text search."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from time import sleep
6
+ from typing import List, Optional
7
+
8
+ from webscout.scout import Scout
9
+
10
+ from ....search.results import TextResult
11
+ from .base import BraveBase
12
+
13
+
14
+ class BraveTextSearch(BraveBase):
15
+ """Brave text/web search."""
16
+
17
+ name = "brave"
18
+ category = "text"
19
+
20
+ def run(self, *args, **kwargs) -> List[TextResult]:
21
+ """Perform text search on Brave using offset pagination.
22
+
23
+ Uses server-rendered HTML and parses result containers with CSS selectors.
24
+ """
25
+ from typing import cast
26
+
27
+ keywords = args[0] if args else kwargs.get("keywords")
28
+ if not keywords:
29
+ raise ValueError("Keywords are mandatory")
30
+
31
+ safesearch = args[2] if len(args) > 2 else kwargs.get("safesearch", "moderate")
32
+ max_results = args[3] if len(args) > 3 else kwargs.get("max_results", 10)
33
+ if max_results is None:
34
+ max_results = 10
35
+
36
+ safesearch_map = {"on": "strict", "moderate": "moderate", "off": "off"}
37
+ safesearch_value = safesearch_map.get(safesearch.lower(), "moderate")
38
+
39
+ start_offset = int(kwargs.get("start_offset", 0))
40
+ offset = start_offset
41
+
42
+ fetched_results: List[TextResult] = []
43
+ fetched_hrefs: set[str] = set()
44
+
45
+ def fetch_html(params: dict) -> str:
46
+ url = f"{self.base_url}/search"
47
+ # Merge session headers to include fingerprint (User-Agent, etc.)
48
+ headers = dict(self.session.headers) if getattr(self, "session", None) else {}
49
+ headers.update({
50
+ "Referer": "https://search.brave.com/",
51
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
52
+ "Accept-Language": "en-US,en;q=0.9",
53
+ })
54
+ attempts = 3
55
+ backoff = 1.0
56
+ last_exc: Exception | None = None
57
+ for attempt in range(attempts):
58
+ try:
59
+ resp = self.session.get(url, params=params, headers=headers, timeout=self.timeout)
60
+ resp.raise_for_status()
61
+ return resp.text
62
+ except Exception as exc: # network or HTTP errors
63
+ last_exc = exc
64
+ # If it's a 429 / transient server error, back off and retry
65
+ try:
66
+ code = getattr(exc, "code", None) or getattr(exc, "status_code", None)
67
+ except Exception:
68
+ code = None
69
+ if code in (429, 500, 502, 503, 504):
70
+ sleep(backoff)
71
+ backoff *= 2
72
+ continue
73
+ # As a last attempt, try a simple GET without params appended (fallback)
74
+ try:
75
+ fallback_resp = self.session.get(url + "?" + "&".join(f"{k}={v}" for k, v in params.items()), headers=headers, timeout=self.timeout)
76
+ fallback_resp.raise_for_status()
77
+ return fallback_resp.text
78
+ except Exception:
79
+ # Final fallback: try using the requests library (less features but sometimes works)
80
+ try:
81
+ import requests
82
+
83
+ r = requests.get(url, params=params, headers=headers, timeout=self.timeout, verify=getattr(self, "verify", True))
84
+ r.raise_for_status()
85
+ return r.text
86
+ except Exception:
87
+ raise Exception(f"Failed to GET {url} with {params}: {exc}") from exc
88
+ raise Exception(f"Failed to GET {url} after retries: {last_exc}") from last_exc
89
+
90
+ # Pagination: offset param is a 0-based page index
91
+ while len(fetched_results) < max_results:
92
+ params = {"q": keywords, "source": "web", "offset": str(offset), "spellcheck": "0"}
93
+ if safesearch_value:
94
+ params["safesearch"] = safesearch_value
95
+
96
+ html = fetch_html(params)
97
+ # Parse and extract results using helper
98
+ page_results = self._parse_results_from_html(html)
99
+
100
+ if not page_results:
101
+ break
102
+
103
+ for res in page_results:
104
+ if len(fetched_results) >= max_results:
105
+ break
106
+ if res.href and res.href not in fetched_hrefs:
107
+ fetched_hrefs.add(res.href)
108
+ fetched_results.append(res)
109
+
110
+ offset += 1
111
+ if self.sleep_interval:
112
+ sleep(self.sleep_interval)
113
+
114
+ return fetched_results[:max_results]
115
+
116
+ def _parse_results_from_html(self, html: str) -> List[TextResult]:
117
+ """Parse HTML and extract text search results.
118
+
119
+ This method is separated for testability.
120
+ """
121
+ soup = Scout(html)
122
+ containers = soup.select("div.result-content")
123
+ results: List[TextResult] = []
124
+
125
+ for container in containers:
126
+ a_elem = container.select_one("a[href]")
127
+ # Title may be in .title.search-snippet-title or nested inside the anchor
128
+ title_elem = container.select_one(".title.search-snippet-title") or (a_elem.select_one(".title") if a_elem else None)
129
+
130
+ # Try multiple snippet locations: inside container, sibling .snippet, parent fallbacks
131
+ body = ""
132
+ candidates = []
133
+ # inside container
134
+ candidates.append(container.select_one(".generic-snippet"))
135
+ candidates.append(container.select_one(".snippet .generic-snippet"))
136
+ candidates.append(container.select_one(".description"))
137
+ candidates.append(container.select_one(".result-snippet"))
138
+ candidates.append(container.select_one("p"))
139
+
140
+ # sibling .snippet
141
+ try:
142
+ fn = getattr(container, "find_next_sibling", None)
143
+ if callable(fn):
144
+ sib = fn("div", class_="snippet")
145
+ if sib:
146
+ candidates.append(sib.select_one(".generic-snippet"))
147
+ except Exception:
148
+ pass
149
+
150
+ # parent-level fallbacks
151
+ if container.parent:
152
+ candidates.append(container.parent.select_one(".snippet .generic-snippet"))
153
+ candidates.append(container.parent.select_one(".generic-snippet"))
154
+
155
+ for c in candidates:
156
+ if c:
157
+ text = c.get_text(strip=True)
158
+ if text:
159
+ body = text
160
+ break
161
+
162
+ if a_elem and title_elem:
163
+ href = a_elem.get("href", "").strip()
164
+ title = title_elem.get_text(strip=True)
165
+ results.append(TextResult(title=title, href=href, body=body))
166
+
167
+ return results
@@ -0,0 +1,364 @@
1
+ """Brave videos search implementation.
2
+
3
+ This module provides a Brave video search engine that parses HTML responses
4
+ from Brave Search to extract video results from YouTube and other video platforms.
5
+
6
+ Example:
7
+ >>> from webscout.search.engines.brave.videos import BraveVideos
8
+ >>> searcher = BraveVideos()
9
+ >>> results = searcher.run("python tutorial", max_results=10)
10
+ >>> for video in results:
11
+ ... print(f"{video.title} - {video.url}")
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from time import sleep
17
+ from typing import Any
18
+
19
+ from webscout.scout import Scout
20
+
21
+ from ....search.results import VideosResult
22
+ from .base import BraveBase
23
+
24
+
25
+ class BraveVideos(BraveBase):
26
+ """Brave videos search engine.
27
+
28
+ Searches Brave Video Search and parses HTML responses to extract
29
+ video results including title, URL, thumbnail, duration, channel,
30
+ description, publish date, and view count.
31
+
32
+ Attributes:
33
+ name: Engine identifier name.
34
+ provider: Provider identifier.
35
+ category: Search category type.
36
+ search_url: Base URL for video search.
37
+ """
38
+
39
+ name = "brave_videos"
40
+ provider = "brave"
41
+ category = "videos"
42
+
43
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
44
+ """Initialize Brave videos search client.
45
+
46
+ Args:
47
+ *args: Positional arguments passed to BraveBase.
48
+ **kwargs: Keyword arguments passed to BraveBase.
49
+ """
50
+ super().__init__(*args, **kwargs)
51
+ self.search_url = f"{self.base_url}/videos"
52
+
53
+ def run(self, *args: Any, **kwargs: Any) -> list[VideosResult]:
54
+ """Run video search on Brave.
55
+
56
+ Args:
57
+ *args: Positional arguments. First arg is the search query.
58
+ **kwargs: Keyword arguments including:
59
+ - keywords: Search query string.
60
+ - region: Region code (e.g., 'us-en').
61
+ - safesearch: Safe search level ('on', 'moderate', 'off').
62
+ - max_results: Maximum number of results to return.
63
+ - timelimit: Time filter for results.
64
+
65
+ Returns:
66
+ List of VideosResult objects containing video information.
67
+
68
+ Raises:
69
+ ValueError: If no keywords are provided.
70
+ Exception: If the HTTP request fails.
71
+ """
72
+ keywords = args[0] if args else kwargs.get("keywords")
73
+ region = args[1] if len(args) > 1 else kwargs.get("region", "us-en")
74
+ safesearch = args[2] if len(args) > 2 else kwargs.get("safesearch", "moderate")
75
+ max_results = args[3] if len(args) > 3 else kwargs.get("max_results", 10)
76
+ timelimit = kwargs.get("timelimit")
77
+
78
+ if max_results is None:
79
+ max_results = 10
80
+
81
+ if not keywords:
82
+ raise ValueError("Keywords are mandatory")
83
+
84
+ safesearch_map = {"on": "strict", "moderate": "moderate", "off": "off"}
85
+ safesearch_str = str(safesearch).lower() if safesearch else "moderate"
86
+ safesearch_value = safesearch_map.get(safesearch_str, "moderate")
87
+
88
+ fetched_results: list[VideosResult] = []
89
+ fetched_urls: set[str] = set()
90
+
91
+ offset = 0
92
+ while len(fetched_results) < max_results:
93
+ params: dict[str, str] = {
94
+ "q": keywords,
95
+ "source": "web",
96
+ "safesearch": safesearch_value,
97
+ "spellcheck": "0",
98
+ }
99
+
100
+ if offset > 0:
101
+ params["offset"] = str(offset)
102
+
103
+ if timelimit:
104
+ params["tf"] = timelimit
105
+
106
+ if region:
107
+ params["region"] = region
108
+
109
+ html = self._fetch_page(params)
110
+ page_results = self._parse_results_from_html(html)
111
+
112
+ if not page_results:
113
+ break
114
+
115
+ for result in page_results:
116
+ if len(fetched_results) >= max_results:
117
+ break
118
+ if result.url and result.url not in fetched_urls:
119
+ fetched_urls.add(result.url)
120
+ fetched_results.append(result)
121
+
122
+ offset += 1
123
+
124
+ if self.sleep_interval:
125
+ sleep(self.sleep_interval)
126
+
127
+ return fetched_results[:max_results]
128
+
129
+ def _fetch_page(self, params: dict[str, str]) -> str:
130
+ """Fetch HTML page from Brave videos search.
131
+
132
+ Args:
133
+ params: Query parameters for the request.
134
+
135
+ Returns:
136
+ HTML content of the response.
137
+
138
+ Raises:
139
+ Exception: If the request fails after retries.
140
+ """
141
+ headers = dict(self.session.headers) if getattr(self, "session", None) else {}
142
+ headers.update({
143
+ "Referer": "https://search.brave.com/",
144
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
145
+ "Accept-Language": "en-US,en;q=0.9",
146
+ })
147
+
148
+ attempts = 3
149
+ backoff = 1.0
150
+ last_exc: Exception | None = None
151
+
152
+ for attempt in range(attempts):
153
+ try:
154
+ resp = self.session.get(
155
+ self.search_url,
156
+ params=params,
157
+ headers=headers,
158
+ timeout=self.timeout,
159
+ )
160
+ resp.raise_for_status()
161
+ return resp.text
162
+ except Exception as exc:
163
+ last_exc = exc
164
+ try:
165
+ code = getattr(exc, "code", None) or getattr(exc, "status_code", None)
166
+ except Exception:
167
+ code = None
168
+ if code in (429, 500, 502, 503, 504):
169
+ sleep(backoff)
170
+ backoff *= 2
171
+ continue
172
+ raise Exception(f"Failed to GET {self.search_url}: {exc}") from exc
173
+
174
+ raise Exception(f"Failed to GET {self.search_url} after retries: {last_exc}") from last_exc
175
+
176
+ def _parse_results_from_html(self, html: str) -> list[VideosResult]:
177
+ """Parse HTML and extract video search results.
178
+
179
+ Args:
180
+ html: Raw HTML content from Brave videos search.
181
+
182
+ Returns:
183
+ List of VideosResult objects parsed from the HTML.
184
+ """
185
+ soup = Scout(html)
186
+ results: list[VideosResult] = []
187
+
188
+ # Video results are in div.video-snippet containers
189
+ containers = soup.select("div.video-snippet")
190
+
191
+ for container in containers:
192
+ try:
193
+ result = self._parse_video_container(container)
194
+ if result and result.url:
195
+ results.append(result)
196
+ except Exception:
197
+ # Skip malformed results
198
+ continue
199
+
200
+ return results
201
+
202
+ def _parse_video_container(self, container: Any) -> VideosResult | None:
203
+ """Parse a single video container element.
204
+
205
+ Args:
206
+ container: Scout element representing a video result container.
207
+
208
+ Returns:
209
+ VideosResult object or None if parsing fails.
210
+ """
211
+ # Get video URL from main link
212
+ url = ""
213
+ link_elem = container.select_one("a[href]")
214
+ if link_elem:
215
+ url = link_elem.get("href", "").strip()
216
+
217
+ if not url:
218
+ return None
219
+
220
+ # Get thumbnail
221
+ thumbnail = ""
222
+ thumb_elem = container.select_one("img.thumb, img.video-thumb")
223
+ if thumb_elem:
224
+ thumbnail = thumb_elem.get("src", "").strip()
225
+
226
+ # Get duration
227
+ duration = ""
228
+ duration_elem = container.select_one(".over-thumbnail-info.duration")
229
+ if duration_elem:
230
+ duration = duration_elem.get_text(strip=True)
231
+
232
+ # Get title from the result header
233
+ title = ""
234
+ title_elem = container.select_one(".snippet-title")
235
+ if title_elem:
236
+ title = title_elem.get_text(strip=True)
237
+
238
+ # Get channel/uploader
239
+ uploader = ""
240
+ channel_elem = container.select_one(".attr.channel")
241
+ if channel_elem:
242
+ uploader = channel_elem.get_text(strip=True)
243
+
244
+ # Get provider (e.g., YouTube)
245
+ provider = ""
246
+ netloc_elem = container.select_one(".netloc.attr")
247
+ if netloc_elem:
248
+ provider = netloc_elem.get_text(strip=True)
249
+
250
+ # Get description
251
+ description = ""
252
+ desc_elem = container.select_one(".snippet-description, p.desc")
253
+ if desc_elem:
254
+ description = desc_elem.get_text(strip=True)
255
+
256
+ # Get publish date
257
+ published = ""
258
+ metrics_elem = container.select_one(".metrics")
259
+ if metrics_elem:
260
+ date_elem = metrics_elem.select_one(".attr:first-child")
261
+ if date_elem:
262
+ published = date_elem.get_text(strip=True)
263
+
264
+ # Get view count from metrics
265
+ view_count = 0
266
+ if metrics_elem:
267
+ view_elems = metrics_elem.select(".attr")
268
+ for elem in view_elems:
269
+ text = elem.get_text(strip=True)
270
+ # Look for view count patterns (e.g., "1.31M", "34.1M", "17K")
271
+ if any(c.isdigit() for c in text) and not any(
272
+ month in text.lower()
273
+ for month in [
274
+ "jan", "feb", "mar", "apr", "may", "jun",
275
+ "jul", "aug", "sep", "oct", "nov", "dec",
276
+ "hour", "day", "week", "month", "year", "ago",
277
+ ]
278
+ ):
279
+ view_count = self._parse_view_count(text)
280
+ break
281
+
282
+ return VideosResult(
283
+ title=title,
284
+ url=url,
285
+ thumbnail=thumbnail,
286
+ duration=duration,
287
+ uploader=uploader,
288
+ publisher=uploader,
289
+ provider=provider,
290
+ description=description,
291
+ published=published,
292
+ statistics={"views": view_count} if view_count else {},
293
+ content=description,
294
+ images={"thumbnail": thumbnail} if thumbnail else {},
295
+ )
296
+
297
+ def _parse_view_count(self, text: str) -> int:
298
+ """Parse view count from text like '1.31M' or '17K'.
299
+
300
+ Args:
301
+ text: View count string with potential suffixes.
302
+
303
+ Returns:
304
+ Integer view count, or 0 if parsing fails.
305
+ """
306
+ try:
307
+ text = text.strip().upper()
308
+ multiplier = 1
309
+
310
+ if text.endswith("K"):
311
+ multiplier = 1000
312
+ text = text[:-1]
313
+ elif text.endswith("M"):
314
+ multiplier = 1_000_000
315
+ text = text[:-1]
316
+ elif text.endswith("B"):
317
+ multiplier = 1_000_000_000
318
+ text = text[:-1]
319
+
320
+ return int(float(text) * multiplier)
321
+ except (ValueError, TypeError):
322
+ return 0
323
+
324
+ def extract_results(self, html_text: str) -> list[VideosResult]:
325
+ """Extract video results from HTML text.
326
+
327
+ This is an alias for _parse_results_from_html for API consistency.
328
+
329
+ Args:
330
+ html_text: Raw HTML content from Brave videos search.
331
+
332
+ Returns:
333
+ List of VideosResult objects parsed from the HTML.
334
+ """
335
+ return self._parse_results_from_html(html_text)
336
+
337
+
338
+ if __name__ == "__main__":
339
+ # Test the BraveVideos search
340
+ print("Testing BraveVideos search...")
341
+
342
+ searcher = BraveVideos(timeout=15)
343
+
344
+ try:
345
+ # Test basic search
346
+ results = searcher.run("python programming tutorial", max_results=5)
347
+
348
+ print(f"\nFound {len(results)} video results:\n")
349
+ for i, video in enumerate(results, 1):
350
+ print(f"{i}. {video.title}")
351
+ print(f" URL: {video.url}")
352
+ print(f" Duration: {video.duration}")
353
+ print(f" Channel: {video.uploader}")
354
+ print(f" Provider: {video.provider}")
355
+ print(f" Published: {video.published}")
356
+ if video.statistics:
357
+ print(f" Views: {video.statistics.get('views', 'N/A')}")
358
+ print(f" Thumbnail: {video.thumbnail[:80]}..." if video.thumbnail else "")
359
+ print()
360
+
361
+ except Exception as e:
362
+ print(f"Error during search: {e}")
363
+ import traceback
364
+ traceback.print_exc()
@@ -0,0 +1,25 @@
1
+ """DuckDuckGo search engines package."""
2
+
3
+ from .answers import DuckDuckGoAnswers
4
+ from .base import DuckDuckGoBase
5
+ from .images import DuckDuckGoImages
6
+ from .maps import DuckDuckGoMaps
7
+ from .news import DuckDuckGoNews
8
+ from .suggestions import DuckDuckGoSuggestions
9
+ from .text import DuckDuckGoTextSearch
10
+ from .translate import DuckDuckGoTranslate
11
+ from .videos import DuckDuckGoVideos
12
+ from .weather import DuckDuckGoWeather
13
+
14
+ __all__ = [
15
+ "DuckDuckGoBase",
16
+ "DuckDuckGoTextSearch",
17
+ "DuckDuckGoImages",
18
+ "DuckDuckGoVideos",
19
+ "DuckDuckGoNews",
20
+ "DuckDuckGoAnswers",
21
+ "DuckDuckGoSuggestions",
22
+ "DuckDuckGoMaps",
23
+ "DuckDuckGoTranslate",
24
+ "DuckDuckGoWeather",
25
+ ]
@@ -0,0 +1,80 @@
1
+ """DuckDuckGo answers search."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import DuckDuckGoBase
6
+
7
+
8
+ class DuckDuckGoAnswers(DuckDuckGoBase):
9
+ """DuckDuckGo instant answers."""
10
+
11
+ name = "duckduckgo"
12
+ category = "answers"
13
+
14
+ def run(self, *args, **kwargs) -> list[dict[str, str]]:
15
+ """Get instant answers from DuckDuckGo.
16
+
17
+ Args:
18
+ keywords: Search query.
19
+
20
+ Returns:
21
+ List of answer dictionaries.
22
+ """
23
+ keywords = args[0] if args else kwargs.get("keywords")
24
+
25
+ assert keywords, "keywords is mandatory"
26
+
27
+ payload = {
28
+ "q": f"what is {keywords}",
29
+ "format": "json",
30
+ }
31
+ resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload).content
32
+ page_data = self.json_loads(resp_content)
33
+
34
+ results = []
35
+ answer = page_data.get("AbstractText")
36
+ url = page_data.get("AbstractURL")
37
+ if answer:
38
+ results.append(
39
+ {
40
+ "icon": None,
41
+ "text": answer,
42
+ "topic": None,
43
+ "url": url,
44
+ }
45
+ )
46
+
47
+ # related
48
+ payload = {
49
+ "q": f"{keywords}",
50
+ "format": "json",
51
+ }
52
+ resp_content = self._get_url("GET", "https://api.duckduckgo.com/", params=payload).content
53
+ resp_json = self.json_loads(resp_content)
54
+ page_data = resp_json.get("RelatedTopics", [])
55
+
56
+ for row in page_data:
57
+ topic = row.get("Name")
58
+ if not topic:
59
+ icon = row["Icon"].get("URL")
60
+ results.append(
61
+ {
62
+ "icon": f"https://duckduckgo.com{icon}" if icon else "",
63
+ "text": row["Text"],
64
+ "topic": None,
65
+ "url": row["FirstURL"],
66
+ }
67
+ )
68
+ else:
69
+ for subrow in row["Topics"]:
70
+ icon = subrow["Icon"].get("URL")
71
+ results.append(
72
+ {
73
+ "icon": f"https://duckduckgo.com{icon}" if icon else "",
74
+ "text": subrow["Text"],
75
+ "topic": topic,
76
+ "url": subrow["FirstURL"],
77
+ }
78
+ )
79
+
80
+ return results