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.
- webscout/AIauto.py +524 -143
- webscout/AIbase.py +247 -123
- webscout/AIutel.py +68 -132
- webscout/Bard.py +1072 -535
- webscout/Extra/GitToolkit/__init__.py +2 -2
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -0
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -0
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -45
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +189 -18
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -682
- webscout/Extra/tempmail/README.md +488 -0
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +237 -304
- webscout/Provider/AISEARCH/README.md +106 -0
- webscout/Provider/AISEARCH/__init__.py +16 -10
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +130 -209
- webscout/Provider/AISEARCH/monica_search.py +200 -246
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -281
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -0
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +343 -173
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +60 -54
- webscout/Provider/GithubChat.py +385 -367
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -670
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -233
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -266
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -381
- webscout/Provider/Netwrck.py +273 -228
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -0
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -0
- webscout/Provider/OPENAI/__init__.py +148 -25
- webscout/Provider/OPENAI/ai4chat.py +348 -0
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/ayle.py +365 -0
- webscout/Provider/OPENAI/base.py +253 -46
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +514 -193
- webscout/Provider/OPENAI/chatsandbox.py +233 -0
- webscout/Provider/OPENAI/deepinfra.py +403 -272
- webscout/Provider/OPENAI/e2b.py +2370 -1350
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +186 -138
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -0
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +100 -104
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -327
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +110 -84
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -0
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -0
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +126 -115
- webscout/Provider/OPENAI/textpollinations.py +218 -133
- webscout/Provider/OPENAI/toolbaz.py +136 -166
- webscout/Provider/OPENAI/typefully.py +419 -0
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -211
- webscout/Provider/OPENAI/wisecat.py +103 -125
- webscout/Provider/OPENAI/writecream.py +185 -156
- webscout/Provider/OPENAI/x0gpt.py +227 -136
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -344
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -0
- webscout/Provider/TTI/__init__.py +37 -12
- webscout/Provider/TTI/base.py +147 -0
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -0
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -0
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -0
- webscout/Provider/TTS/README.md +186 -0
- webscout/Provider/TTS/__init__.py +43 -7
- webscout/Provider/TTS/base.py +523 -0
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -0
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -180
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +221 -121
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -199
- webscout/Provider/TypliAI.py +311 -0
- webscout/Provider/UNFINISHED/ChatHub.py +219 -0
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +160 -145
- webscout/Provider/UNFINISHED/GizAI.py +300 -0
- webscout/Provider/UNFINISHED/Marcus.py +218 -0
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/UNFINISHED/XenAI.py +330 -0
- webscout/Provider/{Youchat.py → UNFINISHED/Youchat.py} +64 -47
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -0
- webscout/Provider/UNFINISHED/samurai.py +231 -0
- webscout/Provider/WiseCat.py +256 -196
- webscout/Provider/WrDoChat.py +390 -0
- webscout/Provider/__init__.py +115 -198
- webscout/Provider/ai4chat.py +181 -202
- webscout/Provider/akashgpt.py +330 -342
- webscout/Provider/cerebras.py +397 -242
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -234
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -266
- webscout/Provider/llama3mitril.py +230 -180
- webscout/Provider/llmchat.py +308 -213
- webscout/Provider/llmchatco.py +321 -311
- webscout/Provider/meta.py +996 -794
- webscout/Provider/oivscode.py +332 -0
- webscout/Provider/searchchat.py +316 -293
- webscout/Provider/sonus.py +264 -208
- webscout/Provider/toolbaz.py +359 -320
- webscout/Provider/turboseek.py +332 -219
- webscout/Provider/typefully.py +262 -280
- webscout/Provider/x0gpt.py +332 -256
- webscout/__init__.py +31 -38
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -293
- webscout/client.py +1497 -0
- webscout/conversation.py +140 -565
- webscout/exceptions.py +383 -339
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +32 -378
- webscout/prompt_manager.py +376 -274
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -0
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -140
- webscout/scout/core/scout.py +800 -568
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -460
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -809
- webscout/swiftcli/core/__init__.py +7 -0
- webscout/swiftcli/core/cli.py +574 -0
- webscout/swiftcli/core/context.py +98 -0
- webscout/swiftcli/core/group.py +268 -0
- webscout/swiftcli/decorators/__init__.py +28 -0
- webscout/swiftcli/decorators/command.py +243 -0
- webscout/swiftcli/decorators/options.py +247 -0
- webscout/swiftcli/decorators/output.py +392 -0
- webscout/swiftcli/exceptions.py +21 -0
- webscout/swiftcli/plugins/__init__.py +9 -0
- webscout/swiftcli/plugins/base.py +134 -0
- webscout/swiftcli/plugins/manager.py +269 -0
- webscout/swiftcli/utils/__init__.py +58 -0
- webscout/swiftcli/utils/formatting.py +251 -0
- webscout/swiftcli/utils/parsing.py +368 -0
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -55
- webscout/zeroart/base.py +70 -60
- webscout/zeroart/effects.py +155 -99
- webscout/zeroart/fonts.py +1799 -816
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.2.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- webscout-2026.1.19.dist-info/entry_points.txt +4 -0
- webscout-2026.1.19.dist-info/top_level.txt +1 -0
- inferno/__init__.py +0 -6
- inferno/__main__.py +0 -9
- inferno/cli.py +0 -6
- webscout/DWEBS.py +0 -477
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -849
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/LLM.py +0 -442
- webscout/Litlogger/__init__.py +0 -67
- webscout/Litlogger/core/__init__.py +0 -6
- webscout/Litlogger/core/level.py +0 -23
- webscout/Litlogger/core/logger.py +0 -165
- webscout/Litlogger/handlers/__init__.py +0 -12
- webscout/Litlogger/handlers/console.py +0 -33
- webscout/Litlogger/handlers/file.py +0 -143
- webscout/Litlogger/handlers/network.py +0 -173
- webscout/Litlogger/styles/__init__.py +0 -7
- webscout/Litlogger/styles/colors.py +0 -249
- webscout/Litlogger/styles/formats.py +0 -458
- webscout/Litlogger/styles/text.py +0 -87
- webscout/Litlogger/utils/__init__.py +0 -6
- webscout/Litlogger/utils/detectors.py +0 -153
- webscout/Litlogger/utils/formatters.py +0 -200
- webscout/Local/__init__.py +0 -12
- webscout/Local/__main__.py +0 -9
- webscout/Local/api.py +0 -576
- webscout/Local/cli.py +0 -516
- webscout/Local/config.py +0 -75
- webscout/Local/llm.py +0 -287
- webscout/Local/model_manager.py +0 -253
- webscout/Local/server.py +0 -721
- webscout/Local/utils.py +0 -93
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -250
- webscout/Provider/AISEARCH/ISou.py +0 -256
- webscout/Provider/AISEARCH/felo_search.py +0 -228
- webscout/Provider/AISEARCH/genspark_search.py +0 -208
- webscout/Provider/AISEARCH/hika_search.py +0 -194
- webscout/Provider/AISEARCH/scira_search.py +0 -324
- webscout/Provider/Aitopia.py +0 -292
- webscout/Provider/AllenAI.py +0 -413
- webscout/Provider/Blackboxai.py +0 -229
- webscout/Provider/C4ai.py +0 -432
- webscout/Provider/ChatGPTClone.py +0 -226
- webscout/Provider/ChatGPTES.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Chatify.py +0 -175
- webscout/Provider/Cloudflare.py +0 -273
- webscout/Provider/DeepSeek.py +0 -196
- webscout/Provider/ElectronHub.py +0 -709
- webscout/Provider/ExaChat.py +0 -342
- webscout/Provider/Free2GPT.py +0 -241
- webscout/Provider/GPTWeb.py +0 -193
- webscout/Provider/Glider.py +0 -211
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -462
- webscout/Provider/Hunyuan.py +0 -272
- webscout/Provider/LambdaChat.py +0 -392
- webscout/Provider/Llama.py +0 -200
- webscout/Provider/Llama3.py +0 -204
- webscout/Provider/Marcus.py +0 -148
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/c4ai.py +0 -367
- webscout/Provider/OPENAI/chatgptclone.py +0 -460
- webscout/Provider/OPENAI/exachat.py +0 -433
- webscout/Provider/OPENAI/freeaichat.py +0 -352
- webscout/Provider/OPENAI/opkfc.py +0 -488
- webscout/Provider/OPENAI/scirachat.py +0 -463
- webscout/Provider/OPENAI/standardinput.py +0 -425
- webscout/Provider/OPENAI/typegpt.py +0 -346
- webscout/Provider/OPENAI/uncovrAI.py +0 -455
- webscout/Provider/OPENAI/venice.py +0 -413
- webscout/Provider/OPENAI/yep.py +0 -327
- webscout/Provider/OpenGPT.py +0 -199
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Phind.py +0 -535
- webscout/Provider/PizzaGPT.py +0 -198
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -278
- webscout/Provider/TTI/AiForce/__init__.py +0 -22
- webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
- webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
- webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
- webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
- webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
- webscout/Provider/TTI/ImgSys/__init__.py +0 -23
- webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
- webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
- webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
- webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
- webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
- webscout/Provider/TTI/Nexra/__init__.py +0 -22
- webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
- webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
- webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
- webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
- webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
- webscout/Provider/TTI/aiarta/__init__.py +0 -2
- webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
- webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
- webscout/Provider/TTI/artbit/__init__.py +0 -22
- webscout/Provider/TTI/artbit/async_artbit.py +0 -155
- webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
- webscout/Provider/TTI/fastflux/__init__.py +0 -22
- webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
- webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
- webscout/Provider/TTI/huggingface/__init__.py +0 -22
- webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
- webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
- webscout/Provider/TTI/piclumen/__init__.py +0 -23
- webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
- webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
- webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
- webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
- webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
- webscout/Provider/TTI/talkai/__init__.py +0 -4
- webscout/Provider/TTI/talkai/async_talkai.py +0 -229
- webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
- webscout/Provider/TTS/gesserit.py +0 -127
- webscout/Provider/TeachAnything.py +0 -187
- webscout/Provider/Venice.py +0 -219
- webscout/Provider/VercelAI.py +0 -234
- webscout/Provider/WebSim.py +0 -228
- webscout/Provider/Writecream.py +0 -211
- webscout/Provider/WritingMate.py +0 -197
- webscout/Provider/aimathgpt.py +0 -189
- webscout/Provider/askmyai.py +0 -158
- webscout/Provider/asksteve.py +0 -203
- webscout/Provider/bagoodex.py +0 -145
- webscout/Provider/chatglm.py +0 -205
- webscout/Provider/copilot.py +0 -428
- webscout/Provider/freeaichat.py +0 -271
- webscout/Provider/gaurish.py +0 -244
- webscout/Provider/geminiprorealtime.py +0 -160
- webscout/Provider/granite.py +0 -187
- webscout/Provider/hermes.py +0 -219
- webscout/Provider/koala.py +0 -268
- webscout/Provider/labyrinth.py +0 -340
- webscout/Provider/lepton.py +0 -194
- webscout/Provider/llamatutor.py +0 -192
- webscout/Provider/multichat.py +0 -325
- webscout/Provider/promptrefine.py +0 -193
- webscout/Provider/scira_chat.py +0 -277
- webscout/Provider/scnet.py +0 -187
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/tutorai.py +0 -252
- webscout/Provider/typegpt.py +0 -232
- webscout/Provider/uncovr.py +0 -312
- webscout/Provider/yep.py +0 -376
- webscout/litprinter/__init__.py +0 -59
- webscout/scout/core.py +0 -881
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1346
- webscout/webscout_search_async.py +0 -877
- webscout/yep_search.py +0 -297
- webscout-8.2.2.dist-info/METADATA +0 -734
- webscout-8.2.2.dist-info/RECORD +0 -309
- webscout-8.2.2.dist-info/entry_points.txt +0 -5
- webscout-8.2.2.dist-info/top_level.txt +0 -3
- webstoken/__init__.py +0 -30
- webstoken/classifier.py +0 -189
- webstoken/keywords.py +0 -216
- webstoken/language.py +0 -128
- webstoken/ner.py +0 -164
- webstoken/normalizer.py +0 -35
- webstoken/processor.py +0 -77
- webstoken/sentiment.py +0 -206
- webstoken/stemmer.py +0 -73
- webstoken/tagger.py +0 -60
- webstoken/tokenizer.py +0 -158
- {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,
|
|
3
|
-
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
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,
|
|
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
|
|
2
|
-
from .
|
|
3
|
-
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from
|
|
7
|
-
from .
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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,
|
|
3
|
-
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Patterns.
|
|
38
|
-
Patterns.
|
|
39
|
-
Patterns.
|
|
40
|
-
Patterns.
|
|
41
|
-
Patterns.
|
|
42
|
-
Patterns.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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}")
|