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
@@ -1,59 +1,59 @@
1
- import re
2
- from typing import Dict, Any
3
-
4
- from .pool import collect
5
- from .utils import dup_filter
6
- from .https import playlist_data
7
- from .patterns import _PlaylistPatterns as Patterns
8
-
9
-
10
- class Playlist:
11
-
12
- def __init__(self, playlist_id: str):
13
- """
14
- Represents a YouTube Playlist
15
-
16
- Parameters
17
- ----------
18
- playlist_id : str
19
- The id or url of the playlist
20
- """
21
- pattern = re.compile('=(.+?)$|^PL(.+?)$')
22
- match = pattern.search(playlist_id)
23
- if not match:
24
- raise ValueError(f'Invalid playlist id: {playlist_id}')
25
- if match.group(1):
26
- self.id = match.group(1)
27
- elif match.group(2):
28
- self.id = 'PL' + match.group(2)
29
- self._playlist_data = playlist_data(self.id)
30
-
31
- def __repr__(self):
32
- return f'<Playlist {self.id}>'
33
-
34
- @property
35
- def metadata(self) -> Dict[str, Any]:
36
- """
37
- Fetches playlist metadata in a dict format
38
-
39
- Returns
40
- -------
41
- Dict
42
- Playlist metadata in a dict format containing keys: id, url, name, video_count, thumbnail,
43
- """
44
- patterns = [
45
- Patterns.name,
46
- Patterns.video_count,
47
- Patterns.thumbnail,
48
- Patterns.video_id,
49
- ]
50
- ext = collect(lambda x: x.findall(self._playlist_data) or None, patterns)
51
- data = [e[0] if e else None for e in ext]
52
- return {
53
- 'id': self.id,
54
- 'url': 'https://www.youtube.com/playlist?list=' + self.id,
55
- 'name': data[0] if data else None,
56
- 'video_count': data[1] if data else None,
57
- 'thumbnail': data[2] if data else None,
58
- 'videos': dup_filter(ext[3])
59
- }
1
+ import re
2
+ from typing import Any, Dict, Optional
3
+
4
+ from .https import playlist_data
5
+ from .patterns import _PlaylistPatterns as Patterns
6
+ from .pool import collect
7
+ from .utils import dup_filter
8
+
9
+
10
+ class Playlist:
11
+
12
+ def __init__(self, playlist_id: str):
13
+ """
14
+ Represents a YouTube Playlist
15
+
16
+ Parameters
17
+ ----------
18
+ playlist_id : str
19
+ The id or url of the playlist
20
+ """
21
+ pattern = re.compile('=(.+?)$|^PL(.+?)$')
22
+ match = pattern.search(playlist_id)
23
+ if not match:
24
+ raise ValueError(f'Invalid playlist id: {playlist_id}')
25
+ if match.group(1):
26
+ self.id = match.group(1)
27
+ elif match.group(2):
28
+ self.id = 'PL' + match.group(2)
29
+ self._playlist_data = playlist_data(self.id)
30
+
31
+ def __repr__(self):
32
+ return f'<Playlist {self.id}>'
33
+
34
+ @property
35
+ def metadata(self) -> Dict[str, Any]:
36
+ """
37
+ Fetches playlist metadata in a dict format
38
+
39
+ Returns
40
+ -------
41
+ Dict
42
+ Playlist metadata in a dict format containing keys: id, url, name, video_count, thumbnail,
43
+ """
44
+ patterns = [
45
+ Patterns.name,
46
+ Patterns.video_count,
47
+ Patterns.thumbnail,
48
+ Patterns.video_id,
49
+ ]
50
+ ext = collect(lambda x: x.findall(self._playlist_data) or None, patterns)
51
+ data = [e[0] if e else None for e in ext]
52
+ return {
53
+ 'id': self.id,
54
+ 'url': 'https://www.youtube.com/playlist?list=' + self.id,
55
+ 'name': data[0] if data else None,
56
+ 'video_count': data[1] if data else None,
57
+ 'thumbnail': data[2] if data else None,
58
+ 'videos': dup_filter(ext[3])
59
+ }
@@ -1,8 +1,8 @@
1
- import concurrent.futures
2
- from typing import Callable, List, Any
3
-
4
-
5
- def collect(func: Callable, args: List[Any]) -> List[Any]:
6
- max_workers = len(args) or 1
7
- with concurrent.futures.ThreadPoolExecutor(max_workers) as exe:
8
- return list(exe.map(func, args))
1
+ import concurrent.futures
2
+ from typing import Any, Callable, List, Optional
3
+
4
+
5
+ def collect(func: Callable, args: List[Any]) -> List[Any]:
6
+ max_workers = len(args) or 1
7
+ with concurrent.futures.ThreadPoolExecutor(max_workers) as exe:
8
+ return list(exe.map(func, args))
@@ -1,40 +1,143 @@
1
- from .utils import dup_filter
2
- from .video import Video
3
- from .channel import Channel
4
- from .playlist import Playlist
5
- from .patterns import _QueryPatterns as Patterns
6
- from typing import Optional, Dict, Any, List
7
- from .https import find_videos, find_channels, find_playlists
8
-
9
-
10
- class Search:
11
-
12
- @staticmethod
13
- def video(keywords: str) -> Optional[Video]:
14
- video_ids = Patterns.video_id.findall(find_videos(keywords))
15
- return Video(video_ids[0]) if video_ids else None
16
-
17
- @staticmethod
18
- def channel(keywords: str) -> Optional[Channel]:
19
- channel_ids = Patterns.channel_id.findall(find_channels(keywords))
20
- return Channel(channel_ids[0]) if channel_ids else None
21
-
22
- @staticmethod
23
- def playlist(keywords: str) -> Optional[Playlist]:
24
- playlist_ids = Patterns.playlist_id.findall(find_playlists(keywords))
25
- return Playlist(playlist_ids[0]) if playlist_ids else None
26
-
27
- @staticmethod
28
- def videos(keywords: str, limit: int = 20) -> Optional[List[str]]:
29
- return dup_filter(Patterns.video_id.findall(find_videos(keywords)), limit)
30
-
31
- @staticmethod
32
- def channels(keywords: str, limit: int = 20) -> Optional[List[str]]:
33
- return dup_filter(Patterns.channel_id.findall(find_channels(keywords)), limit)
34
-
35
- @staticmethod
36
- def playlists(keywords: str, limit: int = 20) -> Optional[List[str]]:
37
- return dup_filter(Patterns.playlist_id.findall(find_playlists(keywords)), limit)
38
-
39
- if __name__ == "__main__":
40
- print(Search.videos("java"))
1
+ from typing import List, Optional
2
+ from urllib.parse import quote
3
+
4
+ from .channel import Channel
5
+ from .https import find_channels, find_playlists, find_videos
6
+ from .patterns import _QueryPatterns as Patterns
7
+ from .playlist import Playlist
8
+ from .utils import dup_filter, request
9
+ from .video import Video
10
+
11
+
12
+ class Search:
13
+
14
+ @staticmethod
15
+ def video(keywords: str) -> Optional[Video]:
16
+ video_ids = Patterns.video_id.findall(find_videos(keywords))
17
+ return Video(video_ids[0]) if video_ids else None
18
+
19
+ @staticmethod
20
+ def channel(keywords: str) -> Optional[Channel]:
21
+ channel_ids = Patterns.channel_id.findall(find_channels(keywords))
22
+ return Channel(channel_ids[0]) if channel_ids else None
23
+
24
+ @staticmethod
25
+ def playlist(keywords: str) -> Optional[Playlist]:
26
+ playlist_ids = Patterns.playlist_id.findall(find_playlists(keywords))
27
+ return Playlist(playlist_ids[0]) if playlist_ids else None
28
+
29
+ @staticmethod
30
+ def videos(keywords: str, limit: int = 20) -> Optional[List[str]]:
31
+ return dup_filter(Patterns.video_id.findall(find_videos(keywords)), limit)
32
+
33
+ @staticmethod
34
+ def channels(keywords: str, limit: int = 20) -> Optional[List[str]]:
35
+ return dup_filter(Patterns.channel_id.findall(find_channels(keywords)), limit)
36
+
37
+ @staticmethod
38
+ def playlists(keywords: str, limit: int = 20) -> Optional[List[str]]:
39
+ return dup_filter(Patterns.playlist_id.findall(find_playlists(keywords)), limit)
40
+
41
+ @staticmethod
42
+ def shorts(keywords: str, limit: int = 20) -> Optional[List[str]]:
43
+ """
44
+ Search for YouTube Shorts.
45
+
46
+ Args:
47
+ keywords: Search query
48
+ limit: Maximum number of results
49
+
50
+ Returns:
51
+ List of video IDs for matching Shorts
52
+ """
53
+ # sp=EgIYAQ%253D%253D is the filter for Shorts
54
+ url = f"https://www.youtube.com/results?search_query={quote(keywords)}&sp=EgIYAQ%253D%253D"
55
+ try:
56
+ html = request(url)
57
+ return dup_filter(Patterns.video_id.findall(html), limit)
58
+ except Exception:
59
+ return []
60
+
61
+ @staticmethod
62
+ def live_streams(keywords: str, limit: int = 20) -> Optional[List[str]]:
63
+ """
64
+ Search for live streams.
65
+
66
+ Args:
67
+ keywords: Search query
68
+ limit: Maximum number of results
69
+
70
+ Returns:
71
+ List of video IDs for live streams
72
+ """
73
+ # sp=EgJAAQ%253D%253D is the filter for live streams
74
+ url = f"https://www.youtube.com/results?search_query={quote(keywords)}&sp=EgJAAQ%253D%253D"
75
+ try:
76
+ html = request(url)
77
+ return dup_filter(Patterns.video_id.findall(html), limit)
78
+ except Exception:
79
+ return []
80
+
81
+ @staticmethod
82
+ def videos_by_duration(keywords: str, duration: str = "short", limit: int = 20) -> Optional[List[str]]:
83
+ """
84
+ Search videos filtered by duration.
85
+
86
+ Args:
87
+ keywords: Search query
88
+ duration: Duration filter - "short" (<4 min), "medium" (4-20 min), "long" (>20 min)
89
+ limit: Maximum number of results
90
+
91
+ Returns:
92
+ List of video IDs
93
+ """
94
+ duration_filters = {
95
+ "short": "EgIYAQ%253D%253D", # Under 4 minutes
96
+ "medium": "EgIYAw%253D%253D", # 4-20 minutes
97
+ "long": "EgIYAg%253D%253D" # Over 20 minutes
98
+ }
99
+ sp = duration_filters.get(duration, "")
100
+ url = f"https://www.youtube.com/results?search_query={quote(keywords)}&sp={sp}"
101
+ try:
102
+ html = request(url)
103
+ return dup_filter(Patterns.video_id.findall(html), limit)
104
+ except Exception:
105
+ return []
106
+
107
+ @staticmethod
108
+ def videos_by_upload_date(keywords: str, when: str = "today", limit: int = 20) -> Optional[List[str]]:
109
+ """
110
+ Search videos filtered by upload date.
111
+
112
+ Args:
113
+ keywords: Search query
114
+ when: Time filter - "hour", "today", "week", "month", "year"
115
+ limit: Maximum number of results
116
+
117
+ Returns:
118
+ List of video IDs
119
+ """
120
+ date_filters = {
121
+ "hour": "EgIIAQ%253D%253D",
122
+ "today": "EgIIAg%253D%253D",
123
+ "week": "EgIIAw%253D%253D",
124
+ "month": "EgIIBA%253D%253D",
125
+ "year": "EgIIBQ%253D%253D"
126
+ }
127
+ sp = date_filters.get(when, "")
128
+ url = f"https://www.youtube.com/results?search_query={quote(keywords)}&sp={sp}"
129
+ try:
130
+ html = request(url)
131
+ return dup_filter(Patterns.video_id.findall(html), limit)
132
+ except Exception:
133
+ return []
134
+
135
+
136
+ if __name__ == "__main__":
137
+ print("Testing Search.shorts:")
138
+ shorts = Search.shorts("funny", 5)
139
+ print(f" Found: {shorts}")
140
+
141
+ print("\nTesting Search.live_streams:")
142
+ live = Search.live_streams("music", 5)
143
+ print(f" Found: {live}")
@@ -0,0 +1,122 @@
1
+ """YouTube Shorts functionality."""
2
+ import re
3
+ from typing import List, Optional
4
+ from urllib.error import HTTPError
5
+ from urllib.request import Request, urlopen
6
+
7
+ from .patterns import _ExtraPatterns as Patterns
8
+ from .utils import dup_filter, request
9
+
10
+ try:
11
+ from webscout.litagent.agent import LitAgent
12
+ _USER_AGENT = LitAgent().random()
13
+ except ImportError:
14
+ _USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
15
+
16
+
17
+ class Shorts:
18
+ """Class for YouTube Shorts operations."""
19
+
20
+ SHORTS_URL = "https://www.youtube.com/shorts"
21
+
22
+ @staticmethod
23
+ def is_short(video_id: str) -> bool:
24
+ """
25
+ Check if a video is a YouTube Short.
26
+
27
+ Args:
28
+ video_id: YouTube video ID
29
+
30
+ Returns:
31
+ True if video is a Short, False otherwise
32
+ """
33
+ if not video_id:
34
+ return False
35
+
36
+ # Clean video ID
37
+ if "youtube.com" in video_id or "youtu.be" in video_id:
38
+ match = re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})', video_id)
39
+ if match:
40
+ video_id = match.group(1)
41
+
42
+ url = f"https://www.youtube.com/shorts/{video_id}"
43
+
44
+ try:
45
+ headers = {
46
+ "User-Agent": _USER_AGENT,
47
+ "Accept": "text/html"
48
+ }
49
+ req = Request(url, headers=headers, method='HEAD')
50
+ response = urlopen(req, timeout=10)
51
+ # If we get a 200 and URL contains /shorts/, it's a Short
52
+ final_url = response.geturl()
53
+ return "/shorts/" in final_url
54
+ except HTTPError as e:
55
+ if e.code == 303 or e.code == 302:
56
+ # Redirect means it's not a Short (redirects to /watch)
57
+ return False
58
+ return False
59
+ except Exception:
60
+ return False
61
+
62
+ @staticmethod
63
+ def get_trending(limit: int = 20) -> List[str]:
64
+ """
65
+ Get trending YouTube Shorts.
66
+
67
+ Args:
68
+ limit: Maximum number of Shorts to return
69
+
70
+ Returns:
71
+ List of video IDs for trending Shorts
72
+ """
73
+ try:
74
+ html = request("https://www.youtube.com/shorts")
75
+ # Find video IDs in shorts context
76
+ pattern = r'"videoId":"([a-zA-Z0-9_-]{11})"'
77
+ video_ids = re.findall(pattern, html)
78
+ return dup_filter(video_ids, limit)
79
+ except Exception:
80
+ return []
81
+
82
+ @staticmethod
83
+ def search(query: str, limit: int = 20) -> List[str]:
84
+ """
85
+ Search for YouTube Shorts.
86
+
87
+ Args:
88
+ query: Search query
89
+ limit: Maximum number of results
90
+
91
+ Returns:
92
+ List of video IDs for matching Shorts
93
+ """
94
+ if not query:
95
+ return []
96
+
97
+ from urllib.parse import quote
98
+ # sp=EgIYAQ%253D%253D is the filter for Shorts
99
+ url = f"https://www.youtube.com/results?search_query={quote(query)}&sp=EgIYAQ%253D%253D"
100
+
101
+ try:
102
+ html = request(url)
103
+ video_ids = Patterns.video_id.findall(html)
104
+ return dup_filter(video_ids, limit)
105
+ except Exception:
106
+ return []
107
+
108
+
109
+ if __name__ == "__main__":
110
+ print("Testing Shorts.is_short:")
111
+ # Test with a known Short ID (you'd replace with an actual Short ID)
112
+ print(f" is_short test: {Shorts.is_short('abc123')}")
113
+
114
+ print("\nTrending Shorts:")
115
+ trending = Shorts.get_trending(5)
116
+ for vid in trending:
117
+ print(f" - {vid}")
118
+
119
+ print("\nSearch Shorts:")
120
+ results = Shorts.search("funny cats", 5)
121
+ for vid in results:
122
+ print(f" - {vid}")
@@ -1,63 +1,68 @@
1
- import re
2
- from typing import Dict, Any
3
-
4
- from .pool import collect
5
- from .https import video_data
6
- from .patterns import _VideoPatterns as Patterns
7
-
8
-
9
- class Video:
10
-
11
- _HEAD = 'https://www.youtube.com/watch?v='
12
-
13
- def __init__(self, video_id: str):
14
- pattern = re.compile('.be/(.*?)$|=(.*?)$|^(\w{11})$') # noqa
15
- self._matched_id = (
16
- pattern.search(video_id).group(1)
17
- or pattern.search(video_id).group(2)
18
- or pattern.search(video_id).group(3)
19
- )
20
- if self._matched_id:
21
- self._url = self._HEAD + self._matched_id
22
- self._video_data = video_data(self._matched_id)
23
- else:
24
- raise ValueError('invalid video id or url')
25
-
26
- def __repr__(self):
27
- return f'<Video {self._url}>'
28
-
29
- @property
30
- def metadata(self) -> Dict[str, Any]:
31
- patterns = [
32
- Patterns.title,
33
- Patterns.views,
34
- Patterns.likes,
35
- Patterns.duration,
36
- Patterns.author_id,
37
- Patterns.upload_date,
38
- Patterns.thumbnail,
39
- Patterns.tags,
40
- Patterns.description,
41
- Patterns.is_streamed,
42
- Patterns.is_premiered
43
- ]
44
- ext = collect(lambda x: x.findall(self._video_data) or None, patterns)
45
- data = [i[0] if i else i for i in ext]
46
- return {
47
- 'title': data[0],
48
- 'id': self._matched_id,
49
- 'views': data[1][:-6] if data[1] else None,
50
- 'likes': data[2],
51
- 'streamed': data[9] is not None,
52
- 'premiered': data[10],
53
- 'duration': int(data[3]) / 1000 if data[3] else None,
54
- 'author': data[4],
55
- 'upload_date': data[5],
56
- 'url': self._url,
57
- 'thumbnail': data[6],
58
- 'tags': data[7].split(',') if data[7] else None,
59
- 'description': data[8].replace('\\n', '\n') if data[8] else None
60
- }
61
-
62
- if __name__ == '__main__':
63
- print(Video('https://www.youtube.com/watch?v=9bZkp7q19f0').metadata)
1
+ import re
2
+ from typing import Any, Dict, Optional
3
+
4
+ from .https import video_data
5
+ from .patterns import _VideoPatterns as Patterns
6
+ from .pool import collect
7
+
8
+
9
+ class Video:
10
+
11
+ _HEAD = 'https://www.youtube.com/watch?v='
12
+
13
+ def __init__(self, video_id: str):
14
+ pattern = re.compile(r'.be/(.*?)$|=(.*?)$|^(\w{11})$') # noqa
15
+ match = pattern.search(video_id)
16
+ if match:
17
+ self._matched_id = (
18
+ match.group(1)
19
+ or match.group(2)
20
+ or match.group(3)
21
+ )
22
+ else:
23
+ self._matched_id = None
24
+
25
+ if self._matched_id:
26
+ self._url = self._HEAD + self._matched_id
27
+ self._video_data = video_data(self._matched_id)
28
+ else:
29
+ raise ValueError('invalid video id or url')
30
+
31
+ def __repr__(self):
32
+ return f'<Video {self._url}>'
33
+
34
+ @property
35
+ def metadata(self) -> Dict[str, Any]:
36
+ patterns = [
37
+ Patterns.title,
38
+ Patterns.views,
39
+ Patterns.likes,
40
+ Patterns.duration,
41
+ Patterns.author_id,
42
+ Patterns.upload_date,
43
+ Patterns.thumbnail,
44
+ Patterns.tags,
45
+ Patterns.description,
46
+ Patterns.is_streamed,
47
+ Patterns.is_premiered
48
+ ]
49
+ ext = collect(lambda x: x.findall(self._video_data) or None, patterns)
50
+ data = [i[0] if i else i for i in ext]
51
+ return {
52
+ 'title': data[0],
53
+ 'id': self._matched_id,
54
+ 'views': data[1][:-6] if data[1] else None,
55
+ 'likes': data[2],
56
+ 'streamed': data[9] is not None,
57
+ 'premiered': data[10],
58
+ 'duration': int(data[3]) / 1000 if data[3] else None,
59
+ 'author': data[4],
60
+ 'upload_date': data[5],
61
+ 'url': self._url,
62
+ 'thumbnail': data[6],
63
+ 'tags': data[7].split(',') if data[7] else None,
64
+ 'description': data[8].replace('\\n', '\n') if data[8] else None
65
+ }
66
+
67
+ if __name__ == '__main__':
68
+ print(Video('https://www.youtube.com/watch?v=9bZkp7q19f0').metadata)
@@ -0,0 +1,97 @@
1
+ """YouTube search suggestions and autocomplete."""
2
+ import json
3
+ from typing import List, Optional
4
+ from urllib.parse import quote
5
+ from urllib.request import Request, urlopen
6
+
7
+ try:
8
+ from webscout.litagent.agent import LitAgent
9
+ _USER_AGENT = LitAgent().random()
10
+ except ImportError:
11
+ _USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
12
+
13
+
14
+ class Suggestions:
15
+ """Class for YouTube search suggestions and autocomplete."""
16
+
17
+ AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"
18
+
19
+ @staticmethod
20
+ def autocomplete(query: str, language: str = "en") -> List[str]:
21
+ """
22
+ Get YouTube autocomplete suggestions for a search query.
23
+
24
+ Args:
25
+ query: Search query to get suggestions for
26
+ language: Language code (e.g., 'en', 'es', 'fr')
27
+
28
+ Returns:
29
+ List of autocomplete suggestions
30
+ """
31
+ if not query:
32
+ return []
33
+
34
+ url = f"{Suggestions.AUTOCOMPLETE_URL}?client=youtube&ds=yt&q={quote(query)}&hl={language}"
35
+
36
+ headers = {
37
+ "User-Agent": _USER_AGENT,
38
+ "Accept": "application/json"
39
+ }
40
+
41
+ try:
42
+ req = Request(url, headers=headers)
43
+ response = urlopen(req, timeout=10)
44
+ data = response.read().decode('utf-8')
45
+
46
+ # Response is JSONP, extract JSON part
47
+ # Format: window.google.ac.h(["query",[["suggestion1"],["suggestion2"],...]])
48
+ start = data.find('(')
49
+ end = data.rfind(')')
50
+ if start != -1 and end != -1:
51
+ json_str = data[start + 1:end]
52
+ parsed = json.loads(json_str)
53
+ if len(parsed) > 1 and isinstance(parsed[1], list):
54
+ return [item[0] for item in parsed[1] if isinstance(item, list) and item]
55
+ return []
56
+ except Exception:
57
+ return []
58
+
59
+ @staticmethod
60
+ def trending_searches(language: str = "en", country: str = "US") -> List[str]:
61
+ """
62
+ Get trending YouTube searches.
63
+
64
+ Args:
65
+ language: Language code
66
+ country: Country code
67
+
68
+ Returns:
69
+ List of trending search terms
70
+ """
71
+ # Get suggestions for empty-ish queries that return trending
72
+ trending = []
73
+ for seed in ["", "how to", "what is", "best"]:
74
+ suggestions = Suggestions.autocomplete(seed, language)
75
+ trending.extend(suggestions[:3])
76
+
77
+ # Remove duplicates while preserving order
78
+ seen = set()
79
+ unique = []
80
+ for item in trending:
81
+ if item not in seen:
82
+ seen.add(item)
83
+ unique.append(item)
84
+
85
+ return unique[:20]
86
+
87
+
88
+ if __name__ == "__main__":
89
+ print("Testing autocomplete:")
90
+ suggestions = Suggestions.autocomplete("python tutorial")
91
+ for s in suggestions[:5]:
92
+ print(f" - {s}")
93
+
94
+ print("\nTrending searches:")
95
+ trending = Suggestions.trending_searches()
96
+ for t in trending[:5]:
97
+ print(f" - {t}")