webscout 8.2.9__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 (413) hide show
  1. webscout/AIauto.py +524 -251
  2. webscout/AIbase.py +247 -319
  3. webscout/AIutel.py +68 -703
  4. webscout/Bard.py +1072 -1026
  5. webscout/Extra/GitToolkit/__init__.py +10 -10
  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 -375
  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 -44
  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 -118
  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 +403 -232
  35. webscout/Extra/__init__.py +2 -3
  36. webscout/Extra/gguf.py +1298 -684
  37. webscout/Extra/tempmail/README.md +487 -487
  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 +292 -333
  49. webscout/Provider/AISEARCH/README.md +106 -279
  50. webscout/Provider/AISEARCH/__init__.py +16 -9
  51. webscout/Provider/AISEARCH/brave_search.py +298 -0
  52. webscout/Provider/AISEARCH/iask_search.py +357 -410
  53. webscout/Provider/AISEARCH/monica_search.py +200 -220
  54. webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
  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 -342
  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 +467 -340
  64. webscout/Provider/EssentialAI.py +217 -0
  65. webscout/Provider/ExaAI.py +274 -261
  66. webscout/Provider/Gemini.py +175 -169
  67. webscout/Provider/GithubChat.py +385 -369
  68. webscout/Provider/Gradient.py +286 -0
  69. webscout/Provider/Groq.py +556 -801
  70. webscout/Provider/HadadXYZ.py +323 -0
  71. webscout/Provider/HeckAI.py +392 -375
  72. webscout/Provider/HuggingFace.py +387 -0
  73. webscout/Provider/IBM.py +340 -0
  74. webscout/Provider/Jadve.py +317 -291
  75. webscout/Provider/K2Think.py +306 -0
  76. webscout/Provider/Koboldai.py +221 -384
  77. webscout/Provider/Netwrck.py +273 -270
  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 -952
  83. webscout/Provider/OPENAI/TogetherAI.py +405 -0
  84. webscout/Provider/OPENAI/TwoAI.py +255 -357
  85. webscout/Provider/OPENAI/__init__.py +148 -40
  86. webscout/Provider/OPENAI/ai4chat.py +348 -293
  87. webscout/Provider/OPENAI/akashgpt.py +436 -0
  88. webscout/Provider/OPENAI/algion.py +303 -0
  89. webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
  90. webscout/Provider/OPENAI/base.py +253 -249
  91. webscout/Provider/OPENAI/cerebras.py +296 -0
  92. webscout/Provider/OPENAI/chatgpt.py +870 -556
  93. webscout/Provider/OPENAI/chatsandbox.py +233 -173
  94. webscout/Provider/OPENAI/deepinfra.py +403 -322
  95. webscout/Provider/OPENAI/e2b.py +2370 -1414
  96. webscout/Provider/OPENAI/elmo.py +278 -0
  97. webscout/Provider/OPENAI/exaai.py +452 -417
  98. webscout/Provider/OPENAI/freeassist.py +446 -0
  99. webscout/Provider/OPENAI/gradient.py +448 -0
  100. webscout/Provider/OPENAI/groq.py +380 -364
  101. webscout/Provider/OPENAI/hadadxyz.py +292 -0
  102. webscout/Provider/OPENAI/heckai.py +333 -308
  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 -335
  107. webscout/Provider/OPENAI/meta.py +541 -0
  108. webscout/Provider/OPENAI/netwrck.py +374 -357
  109. webscout/Provider/OPENAI/nvidia.py +317 -0
  110. webscout/Provider/OPENAI/oivscode.py +348 -287
  111. webscout/Provider/OPENAI/openrouter.py +328 -0
  112. webscout/Provider/OPENAI/pydantic_imports.py +1 -172
  113. webscout/Provider/OPENAI/sambanova.py +397 -0
  114. webscout/Provider/OPENAI/sonus.py +305 -304
  115. webscout/Provider/OPENAI/textpollinations.py +370 -339
  116. webscout/Provider/OPENAI/toolbaz.py +375 -413
  117. webscout/Provider/OPENAI/typefully.py +419 -355
  118. webscout/Provider/OPENAI/typliai.py +279 -0
  119. webscout/Provider/OPENAI/utils.py +314 -318
  120. webscout/Provider/OPENAI/wisecat.py +359 -387
  121. webscout/Provider/OPENAI/writecream.py +185 -163
  122. webscout/Provider/OPENAI/x0gpt.py +462 -365
  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 -429
  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 -82
  133. webscout/Provider/TTI/__init__.py +37 -7
  134. webscout/Provider/TTI/base.py +147 -64
  135. webscout/Provider/TTI/claudeonline.py +393 -0
  136. webscout/Provider/TTI/magicstudio.py +292 -201
  137. webscout/Provider/TTI/miragic.py +180 -0
  138. webscout/Provider/TTI/pollinations.py +331 -221
  139. webscout/Provider/TTI/together.py +334 -0
  140. webscout/Provider/TTI/utils.py +14 -11
  141. webscout/Provider/TTS/README.md +186 -192
  142. webscout/Provider/TTS/__init__.py +43 -10
  143. webscout/Provider/TTS/base.py +523 -159
  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 -129
  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 -580
  153. webscout/Provider/TTS/streamElements.py +275 -333
  154. webscout/Provider/TTS/utils.py +280 -280
  155. webscout/Provider/TextPollinationsAI.py +331 -308
  156. webscout/Provider/TogetherAI.py +450 -0
  157. webscout/Provider/TwoAI.py +309 -475
  158. webscout/Provider/TypliAI.py +311 -305
  159. webscout/Provider/UNFINISHED/ChatHub.py +219 -209
  160. webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
  161. webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
  162. webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
  163. webscout/Provider/UNFINISHED/Qodo.py +481 -0
  164. webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
  165. webscout/Provider/UNFINISHED/Youchat.py +347 -330
  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 -263
  170. webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
  171. webscout/Provider/WiseCat.py +256 -233
  172. webscout/Provider/WrDoChat.py +390 -370
  173. webscout/Provider/__init__.py +115 -174
  174. webscout/Provider/ai4chat.py +181 -174
  175. webscout/Provider/akashgpt.py +330 -335
  176. webscout/Provider/cerebras.py +397 -290
  177. webscout/Provider/cleeai.py +236 -213
  178. webscout/Provider/elmo.py +291 -283
  179. webscout/Provider/geminiapi.py +343 -208
  180. webscout/Provider/julius.py +245 -223
  181. webscout/Provider/learnfastai.py +333 -325
  182. webscout/Provider/llama3mitril.py +230 -215
  183. webscout/Provider/llmchat.py +308 -258
  184. webscout/Provider/llmchatco.py +321 -306
  185. webscout/Provider/meta.py +996 -801
  186. webscout/Provider/oivscode.py +332 -309
  187. webscout/Provider/searchchat.py +316 -292
  188. webscout/Provider/sonus.py +264 -258
  189. webscout/Provider/toolbaz.py +359 -353
  190. webscout/Provider/turboseek.py +332 -266
  191. webscout/Provider/typefully.py +262 -202
  192. webscout/Provider/x0gpt.py +332 -299
  193. webscout/__init__.py +31 -39
  194. webscout/__main__.py +5 -5
  195. webscout/cli.py +585 -524
  196. webscout/client.py +1497 -70
  197. webscout/conversation.py +140 -436
  198. webscout/exceptions.py +383 -362
  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 +74 -420
  204. webscout/prompt_manager.py +376 -288
  205. webscout/sanitize.py +1514 -0
  206. webscout/scout/README.md +452 -404
  207. webscout/scout/__init__.py +8 -8
  208. webscout/scout/core/__init__.py +7 -7
  209. webscout/scout/core/crawler.py +330 -210
  210. webscout/scout/core/scout.py +800 -607
  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 -478
  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 -95
  284. webscout/swiftcli/core/__init__.py +7 -7
  285. webscout/swiftcli/core/cli.py +574 -297
  286. webscout/swiftcli/core/context.py +98 -104
  287. webscout/swiftcli/core/group.py +268 -241
  288. webscout/swiftcli/decorators/__init__.py +28 -28
  289. webscout/swiftcli/decorators/command.py +243 -221
  290. webscout/swiftcli/decorators/options.py +247 -220
  291. webscout/swiftcli/decorators/output.py +392 -252
  292. webscout/swiftcli/exceptions.py +21 -21
  293. webscout/swiftcli/plugins/__init__.py +9 -9
  294. webscout/swiftcli/plugins/base.py +134 -135
  295. webscout/swiftcli/plugins/manager.py +269 -269
  296. webscout/swiftcli/utils/__init__.py +58 -59
  297. webscout/swiftcli/utils/formatting.py +251 -252
  298. webscout/swiftcli/utils/parsing.py +368 -267
  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 -135
  304. webscout/zeroart/base.py +70 -66
  305. webscout/zeroart/effects.py +155 -101
  306. webscout/zeroart/fonts.py +1799 -1239
  307. webscout-2026.1.19.dist-info/METADATA +638 -0
  308. webscout-2026.1.19.dist-info/RECORD +312 -0
  309. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
  310. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
  311. webscout/DWEBS.py +0 -520
  312. webscout/Extra/Act.md +0 -309
  313. webscout/Extra/GitToolkit/gitapi/README.md +0 -110
  314. webscout/Extra/autocoder/__init__.py +0 -9
  315. webscout/Extra/autocoder/autocoder.py +0 -1105
  316. webscout/Extra/autocoder/autocoder_utiles.py +0 -332
  317. webscout/Extra/gguf.md +0 -430
  318. webscout/Extra/weather.md +0 -281
  319. webscout/Litlogger/README.md +0 -10
  320. webscout/Litlogger/__init__.py +0 -15
  321. webscout/Litlogger/formats.py +0 -4
  322. webscout/Litlogger/handlers.py +0 -103
  323. webscout/Litlogger/levels.py +0 -13
  324. webscout/Litlogger/logger.py +0 -92
  325. webscout/Provider/AI21.py +0 -177
  326. webscout/Provider/AISEARCH/DeepFind.py +0 -254
  327. webscout/Provider/AISEARCH/felo_search.py +0 -202
  328. webscout/Provider/AISEARCH/genspark_search.py +0 -324
  329. webscout/Provider/AISEARCH/hika_search.py +0 -186
  330. webscout/Provider/AISEARCH/scira_search.py +0 -298
  331. webscout/Provider/Aitopia.py +0 -316
  332. webscout/Provider/AllenAI.py +0 -440
  333. webscout/Provider/Blackboxai.py +0 -791
  334. webscout/Provider/ChatGPTClone.py +0 -237
  335. webscout/Provider/ChatGPTGratis.py +0 -194
  336. webscout/Provider/Cloudflare.py +0 -324
  337. webscout/Provider/ExaChat.py +0 -358
  338. webscout/Provider/Flowith.py +0 -217
  339. webscout/Provider/FreeGemini.py +0 -250
  340. webscout/Provider/Glider.py +0 -225
  341. webscout/Provider/HF_space/__init__.py +0 -0
  342. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  343. webscout/Provider/HuggingFaceChat.py +0 -469
  344. webscout/Provider/Hunyuan.py +0 -283
  345. webscout/Provider/LambdaChat.py +0 -411
  346. webscout/Provider/Llama3.py +0 -259
  347. webscout/Provider/Nemotron.py +0 -218
  348. webscout/Provider/OLLAMA.py +0 -396
  349. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
  350. webscout/Provider/OPENAI/Cloudflare.py +0 -378
  351. webscout/Provider/OPENAI/FreeGemini.py +0 -283
  352. webscout/Provider/OPENAI/NEMOTRON.py +0 -232
  353. webscout/Provider/OPENAI/Qwen3.py +0 -283
  354. webscout/Provider/OPENAI/api.py +0 -969
  355. webscout/Provider/OPENAI/c4ai.py +0 -373
  356. webscout/Provider/OPENAI/chatgptclone.py +0 -494
  357. webscout/Provider/OPENAI/copilot.py +0 -242
  358. webscout/Provider/OPENAI/flowith.py +0 -162
  359. webscout/Provider/OPENAI/freeaichat.py +0 -359
  360. webscout/Provider/OPENAI/mcpcore.py +0 -389
  361. webscout/Provider/OPENAI/multichat.py +0 -376
  362. webscout/Provider/OPENAI/opkfc.py +0 -496
  363. webscout/Provider/OPENAI/scirachat.py +0 -477
  364. webscout/Provider/OPENAI/standardinput.py +0 -433
  365. webscout/Provider/OPENAI/typegpt.py +0 -364
  366. webscout/Provider/OPENAI/uncovrAI.py +0 -463
  367. webscout/Provider/OPENAI/venice.py +0 -431
  368. webscout/Provider/OPENAI/yep.py +0 -382
  369. webscout/Provider/OpenGPT.py +0 -209
  370. webscout/Provider/Perplexitylabs.py +0 -415
  371. webscout/Provider/Reka.py +0 -214
  372. webscout/Provider/StandardInput.py +0 -290
  373. webscout/Provider/TTI/aiarta.py +0 -365
  374. webscout/Provider/TTI/artbit.py +0 -0
  375. webscout/Provider/TTI/fastflux.py +0 -200
  376. webscout/Provider/TTI/piclumen.py +0 -203
  377. webscout/Provider/TTI/pixelmuse.py +0 -225
  378. webscout/Provider/TTS/gesserit.py +0 -128
  379. webscout/Provider/TTS/sthir.py +0 -94
  380. webscout/Provider/TeachAnything.py +0 -229
  381. webscout/Provider/UNFINISHED/puterjs.py +0 -635
  382. webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
  383. webscout/Provider/Venice.py +0 -258
  384. webscout/Provider/VercelAI.py +0 -253
  385. webscout/Provider/Writecream.py +0 -246
  386. webscout/Provider/WritingMate.py +0 -269
  387. webscout/Provider/asksteve.py +0 -220
  388. webscout/Provider/chatglm.py +0 -215
  389. webscout/Provider/copilot.py +0 -425
  390. webscout/Provider/freeaichat.py +0 -285
  391. webscout/Provider/granite.py +0 -235
  392. webscout/Provider/hermes.py +0 -266
  393. webscout/Provider/koala.py +0 -170
  394. webscout/Provider/lmarena.py +0 -198
  395. webscout/Provider/multichat.py +0 -364
  396. webscout/Provider/scira_chat.py +0 -299
  397. webscout/Provider/scnet.py +0 -243
  398. webscout/Provider/talkai.py +0 -194
  399. webscout/Provider/typegpt.py +0 -289
  400. webscout/Provider/uncovr.py +0 -368
  401. webscout/Provider/yep.py +0 -389
  402. webscout/litagent/Readme.md +0 -276
  403. webscout/litprinter/__init__.py +0 -59
  404. webscout/swiftcli/Readme.md +0 -323
  405. webscout/tempid.py +0 -128
  406. webscout/webscout_search.py +0 -1184
  407. webscout/webscout_search_async.py +0 -654
  408. webscout/yep_search.py +0 -347
  409. webscout/zeroart/README.md +0 -89
  410. webscout-8.2.9.dist-info/METADATA +0 -1033
  411. webscout-8.2.9.dist-info/RECORD +0 -289
  412. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
  413. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
@@ -1,232 +1,403 @@
1
- import re
2
- import json
3
- from typing import Dict, Any
4
- from .https import video_data
5
-
6
-
7
- class Video:
8
-
9
- _HEAD = 'https://www.youtube.com/watch?v='
10
-
11
- def __init__(self, video_id: str):
12
- """
13
- Represents a YouTube video
14
-
15
- Parameters
16
- ----------
17
- video_id : str
18
- The id or url of the video
19
- """
20
- pattern = re.compile('.be/(.*?)$|=(.*?)$|^(\w{11})$') # noqa
21
- match = pattern.search(video_id)
22
-
23
- if not match:
24
- raise ValueError('Invalid YouTube video ID or URL')
25
-
26
- self._matched_id = (
27
- match.group(1)
28
- or match.group(2)
29
- or match.group(3)
30
- )
31
-
32
- if self._matched_id:
33
- self._url = self._HEAD + self._matched_id
34
- self._video_data = video_data(self._matched_id)
35
- # Extract basic info for fallback
36
- title_match = re.search('<title>(.*?) - YouTube</title>', self._video_data)
37
- self.title = title_match.group(1) if title_match else None
38
- self.id = self._matched_id
39
- else:
40
- raise ValueError('Invalid YouTube video ID or URL')
41
-
42
- def __repr__(self):
43
- return f'<Video {self._url}>'
44
-
45
- @property
46
- def metadata(self) -> Dict[str, Any]:
47
- """
48
- Fetches video metadata in a dict format
49
-
50
- Returns
51
- -------
52
- Dict
53
- Video metadata in a dict format containing keys: title, id, views, duration, author_id,
54
- upload_date, url, thumbnails, tags, description, likes, genre, etc.
55
- """
56
- # Multiple patterns to try for video details extraction for robustness
57
- details_patterns = [
58
- re.compile('videoDetails\":(.*?)\"isLiveContent\":.*?}'),
59
- re.compile('videoDetails\":(.*?),\"playerConfig'),
60
- re.compile('videoDetails\":(.*?),\"playabilityStatus')
61
- ]
62
-
63
- # Other metadata patterns
64
- upload_date_pattern = re.compile("<meta itemprop=\"uploadDate\" content=\"(.*?)\">")
65
- genre_pattern = re.compile("<meta itemprop=\"genre\" content=\"(.*?)\">")
66
- like_count_patterns = [
67
- re.compile("iconType\":\"LIKE\"},\"defaultText\":(.*?)}"),
68
- re.compile('\"likeCount\":\"(\\d+)\"')
69
- ]
70
- channel_name_pattern = re.compile('"ownerChannelName":"(.*?)"')
71
-
72
- # Try each pattern for video details
73
- raw_details_match = None
74
- for pattern in details_patterns:
75
- match = pattern.search(self._video_data)
76
- if match:
77
- raw_details_match = match
78
- break
79
-
80
- if not raw_details_match:
81
- # Fallback metadata for search results or incomplete video data
82
- return {
83
- 'title': getattr(self, 'title', None),
84
- 'id': getattr(self, 'id', None),
85
- 'views': getattr(self, 'views', None),
86
- 'streamed': False,
87
- 'duration': None,
88
- 'author_id': None,
89
- 'author_name': None,
90
- 'upload_date': None,
91
- 'url': f"https://www.youtube.com/watch?v={getattr(self, 'id', '')}" if hasattr(self, 'id') else None,
92
- 'thumbnails': None,
93
- 'tags': None,
94
- 'description': None,
95
- 'likes': None,
96
- 'genre': None,
97
- 'is_age_restricted': 'age-restricted' in self._video_data.lower(),
98
- 'is_unlisted': 'unlisted' in self._video_data.lower()
99
- }
100
-
101
- raw_details = raw_details_match.group(0)
102
-
103
- # Extract upload date
104
- upload_date_match = upload_date_pattern.search(self._video_data)
105
- upload_date = upload_date_match.group(1) if upload_date_match else None
106
-
107
- # Extract channel name
108
- channel_name_match = channel_name_pattern.search(self._video_data)
109
- channel_name = channel_name_match.group(1) if channel_name_match else None
110
-
111
- # Parse video details
112
- try:
113
- # Clean up the JSON string for parsing
114
- clean_json = raw_details.replace('videoDetails\":', '')
115
- # Handle potential JSON parsing issues
116
- if clean_json.endswith(','):
117
- clean_json = clean_json[:-1]
118
- metadata = json.loads(clean_json)
119
-
120
- data = {
121
- 'title': metadata.get('title'),
122
- 'id': metadata.get('videoId', self._matched_id),
123
- 'views': metadata.get('viewCount'),
124
- 'streamed': metadata.get('isLiveContent', False),
125
- 'duration': metadata.get('lengthSeconds'),
126
- 'author_id': metadata.get('channelId'),
127
- 'author_name': channel_name or metadata.get('author'),
128
- 'upload_date': upload_date,
129
- 'url': f"https://www.youtube.com/watch?v={metadata.get('videoId', self._matched_id)}",
130
- 'thumbnails': metadata.get('thumbnail', {}).get('thumbnails'),
131
- 'tags': metadata.get('keywords'),
132
- 'description': metadata.get('shortDescription'),
133
- 'is_age_restricted': metadata.get('isAgeRestricted', False) or 'age-restricted' in self._video_data.lower(),
134
- 'is_unlisted': 'unlisted' in self._video_data.lower(),
135
- 'is_family_safe': metadata.get('isFamilySafe', True),
136
- 'is_private': metadata.get('isPrivate', False),
137
- 'is_live_content': metadata.get('isLiveContent', False),
138
- 'is_crawlable': metadata.get('isCrawlable', True),
139
- 'allow_ratings': metadata.get('allowRatings', True)
140
- }
141
- except (json.JSONDecodeError, KeyError, TypeError) as e:
142
- # Fallback to basic metadata if JSON parsing fails
143
- return {
144
- 'title': getattr(self, 'title', None),
145
- 'id': self._matched_id,
146
- 'url': self._url,
147
- 'error': f"Failed to parse video details: {str(e)}"
148
- }
149
-
150
- # Try to extract likes count
151
- likes = None
152
- for pattern in like_count_patterns:
153
- try:
154
- likes_match = pattern.search(self._video_data)
155
- if likes_match:
156
- likes_text = likes_match.group(1)
157
- # Handle different formats of like count
158
- if '{' in likes_text:
159
- likes = json.loads(likes_text + '}}}')['accessibility']['accessibilityData']['label'].split(' ')[0].replace(',', '')
160
- else:
161
- likes = likes_text
162
- break
163
- except (AttributeError, KeyError, json.decoder.JSONDecodeError):
164
- continue
165
-
166
- data['likes'] = likes
167
-
168
- # Try to extract genre
169
- try:
170
- genre_match = genre_pattern.search(self._video_data)
171
- data['genre'] = genre_match.group(1) if genre_match else None
172
- except AttributeError:
173
- data['genre'] = None
174
-
175
- return data
176
-
177
-
178
-
179
- @property
180
- def embed_html(self) -> str:
181
- """
182
- Get the embed HTML code for this video
183
-
184
- Returns:
185
- HTML iframe code for embedding the video
186
- """
187
- return f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{self._matched_id}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
188
-
189
- @property
190
- def embed_url(self) -> str:
191
- """
192
- Get the embed URL for this video
193
-
194
- Returns:
195
- URL for embedding the video
196
- """
197
- return f'https://www.youtube.com/embed/{self._matched_id}'
198
-
199
- @property
200
- def thumbnail_url(self) -> str:
201
- """
202
- Get the thumbnail URL for this video
203
-
204
- Returns:
205
- URL of the video thumbnail (high quality)
206
- """
207
- return f'https://i.ytimg.com/vi/{self._matched_id}/hqdefault.jpg'
208
-
209
- @property
210
- def thumbnail_urls(self) -> Dict[str, str]:
211
- """
212
- Get all thumbnail URLs for this video in different qualities
213
-
214
- Returns:
215
- Dictionary of thumbnail URLs with quality labels
216
- """
217
- return {
218
- 'default': f'https://i.ytimg.com/vi/{self._matched_id}/default.jpg',
219
- 'medium': f'https://i.ytimg.com/vi/{self._matched_id}/mqdefault.jpg',
220
- 'high': f'https://i.ytimg.com/vi/{self._matched_id}/hqdefault.jpg',
221
- 'standard': f'https://i.ytimg.com/vi/{self._matched_id}/sddefault.jpg',
222
- 'maxres': f'https://i.ytimg.com/vi/{self._matched_id}/maxresdefault.jpg'
223
- }
224
-
225
- if __name__ == '__main__':
226
- video = Video('https://www.youtube.com/watch?v=9bZkp7q19f0')
227
- print(video.metadata)
228
-
229
- # Example of getting comments
230
- print("\nFirst 3 comments:")
231
- for i, comment in enumerate(video.stream_comments(3), 1):
232
- print(f"{i}. {comment['author']}: {comment['text'][:50]}...")
1
+ import json
2
+ import re
3
+ from typing import Any, Dict, Generator, List, Optional
4
+
5
+ from .https import video_data
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 Video:
15
+
16
+ _HEAD = 'https://www.youtube.com/watch?v='
17
+
18
+ def __init__(self, video_id: str):
19
+ """
20
+ Represents a YouTube video
21
+
22
+ Parameters
23
+ ----------
24
+ video_id : str
25
+ The id or url of the video
26
+ """
27
+ pattern = re.compile(r'.be/(.*?)$|=(.*?)$|shorts/(.*?)$|^(\w{11})$') # noqa
28
+ match = pattern.search(video_id)
29
+
30
+ if not match:
31
+ raise ValueError('Invalid YouTube video ID or URL')
32
+
33
+ self._matched_id = (
34
+ match.group(1)
35
+ or match.group(2)
36
+ or match.group(3)
37
+ or match.group(4)
38
+ )
39
+
40
+ if self._matched_id:
41
+ self._url = self._HEAD + self._matched_id
42
+ self._video_data = video_data(self._matched_id)
43
+ # Extract basic info for fallback
44
+ title_match = re.search(r'<title>(.*?) - YouTube</title>', self._video_data)
45
+ self.title = title_match.group(1) if title_match else None
46
+ self.id = self._matched_id
47
+ else:
48
+ raise ValueError('Invalid YouTube video ID or URL')
49
+
50
+ def __repr__(self):
51
+ return f'<Video {self._url}>'
52
+
53
+ @property
54
+ def metadata(self) -> Dict[str, Any]:
55
+ """
56
+ Fetches video metadata in a dict format
57
+
58
+ Returns
59
+ -------
60
+ Dict
61
+ Video metadata in a dict format containing keys: title, id, views, duration, author_id,
62
+ upload_date, url, thumbnails, tags, description, likes, genre, etc.
63
+ """
64
+ # Multiple patterns to try for video details extraction for robustness
65
+ details_patterns = [
66
+ re.compile(r'videoDetails\":(.*?)\"isLiveContent\":.*?}'),
67
+ re.compile(r'videoDetails\":(.*?),\"playerConfig'),
68
+ re.compile(r'videoDetails\":(.*?),\"playabilityStatus')
69
+ ]
70
+
71
+ # Other metadata patterns
72
+ upload_date_pattern = re.compile(r"<meta itemprop=\"uploadDate\" content=\"(.*?)\">")
73
+ genre_pattern = re.compile(r"<meta itemprop=\"genre\" content=\"(.*?)\">")
74
+ like_count_patterns = [
75
+ re.compile(r"iconType\":\"LIKE\"},\"defaultText\":(.*?)}"),
76
+ re.compile(r'\"likeCount\":\"(\d+)\"')
77
+ ]
78
+ channel_name_pattern = re.compile(r'"ownerChannelName":"(.*?)"')
79
+
80
+ # Try each pattern for video details
81
+ raw_details_match = None
82
+ for pattern in details_patterns:
83
+ match = pattern.search(self._video_data)
84
+ if match:
85
+ raw_details_match = match
86
+ break
87
+
88
+ if not raw_details_match:
89
+ # Fallback metadata for search results or incomplete video data
90
+ return {
91
+ 'title': getattr(self, 'title', None),
92
+ 'id': getattr(self, 'id', None),
93
+ 'views': getattr(self, 'views', None),
94
+ 'streamed': False,
95
+ 'duration': None,
96
+ 'author_id': None,
97
+ 'author_name': None,
98
+ 'upload_date': None,
99
+ 'url': f"https://www.youtube.com/watch?v={getattr(self, 'id', '')}" if hasattr(self, 'id') else None,
100
+ 'thumbnails': None,
101
+ 'tags': None,
102
+ 'description': None,
103
+ 'likes': None,
104
+ 'genre': None,
105
+ 'is_age_restricted': 'age-restricted' in self._video_data.lower(),
106
+ 'is_unlisted': 'unlisted' in self._video_data.lower()
107
+ }
108
+
109
+ raw_details = raw_details_match.group(0)
110
+
111
+ # Extract upload date
112
+ upload_date_match = upload_date_pattern.search(self._video_data)
113
+ upload_date = upload_date_match.group(1) if upload_date_match else None
114
+
115
+ # Extract channel name
116
+ channel_name_match = channel_name_pattern.search(self._video_data)
117
+ channel_name = channel_name_match.group(1) if channel_name_match else None
118
+
119
+ # Parse video details
120
+ try:
121
+ # Clean up the JSON string for parsing
122
+ clean_json = raw_details.replace('videoDetails\":', '')
123
+ # Handle potential JSON parsing issues
124
+ if clean_json.endswith(','):
125
+ clean_json = clean_json[:-1]
126
+ metadata = json.loads(clean_json)
127
+
128
+ data = {
129
+ 'title': metadata.get('title'),
130
+ 'id': metadata.get('videoId', self._matched_id),
131
+ 'views': metadata.get('viewCount'),
132
+ 'streamed': metadata.get('isLiveContent', False),
133
+ 'duration': metadata.get('lengthSeconds'),
134
+ 'author_id': metadata.get('channelId'),
135
+ 'author_name': channel_name or metadata.get('author'),
136
+ 'upload_date': upload_date,
137
+ 'url': f"https://www.youtube.com/watch?v={metadata.get('videoId', self._matched_id)}",
138
+ 'thumbnails': metadata.get('thumbnail', {}).get('thumbnails'),
139
+ 'tags': metadata.get('keywords'),
140
+ 'description': metadata.get('shortDescription'),
141
+ 'is_age_restricted': metadata.get('isAgeRestricted', False) or 'age-restricted' in self._video_data.lower(),
142
+ 'is_unlisted': 'unlisted' in self._video_data.lower(),
143
+ 'is_family_safe': metadata.get('isFamilySafe', True),
144
+ 'is_private': metadata.get('isPrivate', False),
145
+ 'is_live_content': metadata.get('isLiveContent', False),
146
+ 'is_crawlable': metadata.get('isCrawlable', True),
147
+ 'allow_ratings': metadata.get('allowRatings', True)
148
+ }
149
+ except (json.JSONDecodeError, KeyError, TypeError) as e:
150
+ # Fallback to basic metadata if JSON parsing fails
151
+ return {
152
+ 'title': getattr(self, 'title', None),
153
+ 'id': self._matched_id,
154
+ 'url': self._url,
155
+ 'error': f"Failed to parse video details: {str(e)}"
156
+ }
157
+
158
+ # Try to extract likes count
159
+ likes = None
160
+ for pattern in like_count_patterns:
161
+ try:
162
+ likes_match = pattern.search(self._video_data)
163
+ if likes_match:
164
+ likes_text = likes_match.group(1)
165
+ # Handle different formats of like count
166
+ if '{' in likes_text:
167
+ likes = json.loads(likes_text + '}}}')['accessibility']['accessibilityData']['label'].split(' ')[0].replace(',', '')
168
+ else:
169
+ likes = likes_text
170
+ break
171
+ except (AttributeError, KeyError, json.decoder.JSONDecodeError):
172
+ continue
173
+
174
+ data['likes'] = likes
175
+
176
+ # Try to extract genre
177
+ try:
178
+ genre_match = genre_pattern.search(self._video_data)
179
+ data['genre'] = genre_match.group(1) if genre_match else None
180
+ except AttributeError:
181
+ data['genre'] = None
182
+
183
+ return data
184
+
185
+ @property
186
+ def embed_html(self) -> str:
187
+ """
188
+ Get the embed HTML code for this video
189
+
190
+ Returns:
191
+ HTML iframe code for embedding the video
192
+ """
193
+ return f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{self._matched_id}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
194
+
195
+ @property
196
+ def embed_url(self) -> str:
197
+ """
198
+ Get the embed URL for this video
199
+
200
+ Returns:
201
+ URL for embedding the video
202
+ """
203
+ return f'https://www.youtube.com/embed/{self._matched_id}'
204
+
205
+ @property
206
+ def thumbnail_url(self) -> str:
207
+ """
208
+ Get the thumbnail URL for this video
209
+
210
+ Returns:
211
+ URL of the video thumbnail (high quality)
212
+ """
213
+ return f'https://i.ytimg.com/vi/{self._matched_id}/hqdefault.jpg'
214
+
215
+ @property
216
+ def thumbnail_urls(self) -> Dict[str, str]:
217
+ """
218
+ Get all thumbnail URLs for this video in different qualities
219
+
220
+ Returns:
221
+ Dictionary of thumbnail URLs with quality labels
222
+ """
223
+ return {
224
+ 'default': f'https://i.ytimg.com/vi/{self._matched_id}/default.jpg',
225
+ 'medium': f'https://i.ytimg.com/vi/{self._matched_id}/mqdefault.jpg',
226
+ 'high': f'https://i.ytimg.com/vi/{self._matched_id}/hqdefault.jpg',
227
+ 'standard': f'https://i.ytimg.com/vi/{self._matched_id}/sddefault.jpg',
228
+ 'maxres': f'https://i.ytimg.com/vi/{self._matched_id}/maxresdefault.jpg'
229
+ }
230
+
231
+ @property
232
+ def is_live(self) -> bool:
233
+ """
234
+ Check if video is currently live.
235
+
236
+ Returns:
237
+ True if video is live, False otherwise
238
+ """
239
+ return '"isLive":true' in self._video_data or '"isLiveNow":true' in self._video_data
240
+
241
+ @property
242
+ def is_short(self) -> bool:
243
+ """
244
+ Check if video is a YouTube Short.
245
+
246
+ Returns:
247
+ True if video is a Short, False otherwise
248
+ """
249
+ # Check duration (Shorts are max 60 seconds)
250
+ meta = self.metadata
251
+ duration = meta.get('duration')
252
+ if duration and int(duration) <= 60:
253
+ # Also check for shorts indicators
254
+ if 'shorts' in self._video_data.lower() or '"shorts"' in self._video_data:
255
+ return True
256
+ return False
257
+
258
+ @property
259
+ def hashtags(self) -> List[str]:
260
+ """
261
+ Get hashtags from video description and title.
262
+
263
+ Returns:
264
+ List of hashtags found
265
+ """
266
+ meta = self.metadata
267
+ text = (meta.get('description', '') or '') + ' ' + (meta.get('title', '') or '')
268
+ pattern = r'#([a-zA-Z0-9_]+)'
269
+ matches = re.findall(pattern, text)
270
+ return list(dict.fromkeys(matches)) # Remove duplicates
271
+
272
+ def get_related_videos(self, limit: int = 10) -> List[str]:
273
+ """
274
+ Get related/suggested videos.
275
+
276
+ Args:
277
+ limit: Maximum number of video IDs to return
278
+
279
+ Returns:
280
+ List of related video IDs
281
+ """
282
+ # Find related videos in the page data
283
+ pattern = r'"watchNextEndScreenRenderer".*?"videoId":"([a-zA-Z0-9_-]{11})"'
284
+ matches = re.findall(pattern, self._video_data)
285
+
286
+ if not matches:
287
+ # Fallback pattern
288
+ pattern = r'"compactVideoRenderer".*?"videoId":"([a-zA-Z0-9_-]{11})"'
289
+ matches = re.findall(pattern, self._video_data)
290
+
291
+ # Remove duplicates and self
292
+ seen = set()
293
+ unique = []
294
+ for vid in matches:
295
+ if vid not in seen and vid != self._matched_id:
296
+ seen.add(vid)
297
+ unique.append(vid)
298
+ if len(unique) >= limit:
299
+ break
300
+
301
+ return unique
302
+
303
+ def get_chapters(self) -> Optional[List[Dict[str, Any]]]:
304
+ """
305
+ Get video chapters if available.
306
+
307
+ Returns:
308
+ List of chapters with title, start_time, and thumbnail, or None
309
+ """
310
+ # Look for chapter data in the page
311
+ pattern = r'"chapterRenderer":\s*\{[^}]*"title":\s*\{\s*"simpleText":\s*"([^"]+)"[^}]*"timeRangeStartMillis":\s*(\d+)'
312
+ matches = re.findall(pattern, self._video_data)
313
+
314
+ if not matches:
315
+ # Alternative pattern
316
+ pattern = r'"chapters":\s*\[(.*?)\]'
317
+ chapter_match = re.search(pattern, self._video_data)
318
+ if chapter_match:
319
+ try:
320
+ # Parse as JSON if possible
321
+ chapters_str = '[' + chapter_match.group(1) + ']'
322
+ chapters_data = json.loads(chapters_str)
323
+ return chapters_data
324
+ except Exception:
325
+ pass
326
+ return None
327
+
328
+ chapters = []
329
+ for title, start_ms in matches:
330
+ chapters.append({
331
+ 'title': title,
332
+ 'start_seconds': int(start_ms) / 1000,
333
+ 'start_time': f"{int(int(start_ms)/1000//60)}:{int(int(start_ms)/1000%60):02d}"
334
+ })
335
+
336
+ return chapters if chapters else None
337
+
338
+ def stream_comments(self, limit: int = 20) -> Generator[Dict[str, Any], None, None]:
339
+ """
340
+ Stream video comments from initial page load.
341
+
342
+ Note: YouTube loads comments dynamically via JavaScript, so this method
343
+ may not find comments for all videos. It works best when YouTube includes
344
+ some initial comments in the page HTML.
345
+
346
+ Args:
347
+ limit: Maximum number of comments to yield
348
+
349
+ Yields:
350
+ Comment dictionaries with author, text, video_id
351
+ """
352
+ # Try multiple patterns for comments data
353
+ patterns = [
354
+ # Pattern for commentRenderer with simpleText
355
+ r'"commentRenderer":\s*\{[^}]*"authorText":\s*\{[^}]*"simpleText":\s*"([^"]+)"[^}]*\}.*?"contentText":\s*\{[^}]*"runs":\s*\[\s*\{[^}]*"text":\s*"([^"]*)"',
356
+ # Pattern for author and contentText
357
+ r'"authorText":\s*\{[^}]*"simpleText":\s*"([^"]+)"[^}]*\}[^}]*"contentText":\s*\{[^}]*"text":\s*"([^"]*)"',
358
+ # Simpler pattern
359
+ r'"authorText":"([^"]+)".*?"contentText":"([^"]*)"',
360
+ ]
361
+
362
+ count = 0
363
+ seen_comments = set()
364
+
365
+ for pattern in patterns:
366
+ if count >= limit:
367
+ break
368
+ matches = re.findall(pattern, self._video_data, re.DOTALL)
369
+ for author, text in matches:
370
+ if count >= limit:
371
+ break
372
+ # Avoid duplicates
373
+ comment_key = (author, text[:50])
374
+ if comment_key in seen_comments:
375
+ continue
376
+ seen_comments.add(comment_key)
377
+
378
+ # Clean up text
379
+ text = text.replace('\\n', '\n')
380
+ text = text.replace('\\u0026', '&')
381
+ text = text.replace('\\u003c', '<')
382
+ text = text.replace('\\u003e', '>')
383
+
384
+ yield {
385
+ 'author': author,
386
+ 'text': text,
387
+ 'video_id': self._matched_id
388
+ }
389
+ count += 1
390
+
391
+
392
+ if __name__ == '__main__':
393
+ video = Video('https://www.youtube.com/watch?v=9bZkp7q19f0')
394
+ print(video.metadata)
395
+ print(f"\nIs Live: {video.is_live}")
396
+ print(f"Is Short: {video.is_short}")
397
+ print(f"Hashtags: {video.hashtags}")
398
+ print(f"Related videos: {video.get_related_videos(5)}")
399
+
400
+ chapters = video.get_chapters()
401
+ if chapters:
402
+ print(f"Chapters: {chapters[:3]}")
403
+
@@ -1,7 +1,6 @@
1
1
  from .gguf import *
2
+ from .GitToolkit import *
3
+ from .tempmail import *
2
4
  from .weather import *
3
5
  from .weather_ascii import *
4
- from .autocoder import *
5
6
  from .YTToolkit import *
6
- from .GitToolkit import *
7
- from .tempmail import *