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.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- 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 -375
- 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 -44
- 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 -118
- 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 +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- 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 +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- 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 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- 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 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- 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 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- 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 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- 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 -129
- 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 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- 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 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- 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 +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- 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 -478
- 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 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- 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 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
webscout/cli.py
CHANGED
|
@@ -1,524 +1,585 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
7
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
@
|
|
375
|
-
@option("--
|
|
376
|
-
@option("--
|
|
377
|
-
@option("--
|
|
378
|
-
@option("--
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
region: str,
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
):
|
|
387
|
-
"""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
except Exception as e:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
3
|
+
|
|
4
|
+
from rich import print as rprint
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from .search import (
|
|
10
|
+
BaseSearch,
|
|
11
|
+
BaseSearchEngine,
|
|
12
|
+
BingSearch,
|
|
13
|
+
BraveSearch,
|
|
14
|
+
DuckDuckGoSearch,
|
|
15
|
+
Mojeek,
|
|
16
|
+
Wikipedia,
|
|
17
|
+
YahooSearch,
|
|
18
|
+
Yandex,
|
|
19
|
+
YepSearch,
|
|
20
|
+
)
|
|
21
|
+
from .swiftcli import CLI, option
|
|
22
|
+
from .version import __version__
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
# Engine mapping
|
|
27
|
+
ENGINES: Dict[str, Union[Type[BaseSearch], Type[BaseSearchEngine]]] = {
|
|
28
|
+
"ddg": DuckDuckGoSearch,
|
|
29
|
+
"duckduckgo": DuckDuckGoSearch,
|
|
30
|
+
"bing": BingSearch,
|
|
31
|
+
"yahoo": YahooSearch,
|
|
32
|
+
"brave": BraveSearch,
|
|
33
|
+
"mojeek": Mojeek,
|
|
34
|
+
"yandex": Yandex,
|
|
35
|
+
"wikipedia": Wikipedia,
|
|
36
|
+
"yep": YepSearch,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_engine(name: str) -> Union[BaseSearch, BaseSearchEngine]:
|
|
41
|
+
cls = ENGINES.get(name.lower())
|
|
42
|
+
if not cls:
|
|
43
|
+
rprint(f"[bold red]Error: Engine '{name}' not supported.[/bold red]")
|
|
44
|
+
rprint(f"Available engines: {', '.join(sorted(set(e for e in ENGINES.keys())))}")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
return cls() # type: ignore[arg-type]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _format_views(count: int) -> str:
|
|
50
|
+
"""Format view count to human readable format."""
|
|
51
|
+
if count >= 1_000_000_000:
|
|
52
|
+
return f"{count / 1_000_000_000:.1f}B"
|
|
53
|
+
elif count >= 1_000_000:
|
|
54
|
+
return f"{count / 1_000_000:.1f}M"
|
|
55
|
+
elif count >= 1_000:
|
|
56
|
+
return f"{count / 1_000:.1f}K"
|
|
57
|
+
return str(count)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _truncate(text: str, max_len: int = 80) -> str:
|
|
61
|
+
"""Truncate text to max length."""
|
|
62
|
+
if not text:
|
|
63
|
+
return ""
|
|
64
|
+
if len(text) <= max_len:
|
|
65
|
+
return text
|
|
66
|
+
return text[: max_len - 3] + "..."
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print_videos(data: List[Any], title: str) -> None:
|
|
70
|
+
"""Print video results in a beautiful format."""
|
|
71
|
+
if not data:
|
|
72
|
+
rprint("[bold yellow]No video results found.[/bold yellow]")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
console.print(f"\n[bold cyan]{title}[/bold cyan]\n")
|
|
76
|
+
|
|
77
|
+
for i, video in enumerate(data, 1):
|
|
78
|
+
# Handle both dict and dataclass objects
|
|
79
|
+
if hasattr(video, "title"):
|
|
80
|
+
v_title = video.title
|
|
81
|
+
v_url = video.url
|
|
82
|
+
v_duration = video.duration
|
|
83
|
+
v_uploader = getattr(video, "uploader", "") or getattr(video, "publisher", "")
|
|
84
|
+
v_provider = getattr(video, "provider", "")
|
|
85
|
+
v_published = getattr(video, "published", "")
|
|
86
|
+
v_stats = getattr(video, "statistics", {}) or {}
|
|
87
|
+
v_views = v_stats.get("views", 0) if isinstance(v_stats, dict) else 0
|
|
88
|
+
else:
|
|
89
|
+
v_title = video.get("title", "")
|
|
90
|
+
v_url = video.get("url", "")
|
|
91
|
+
v_duration = video.get("duration", "")
|
|
92
|
+
v_uploader = video.get("uploader", "") or video.get("publisher", "")
|
|
93
|
+
v_provider = video.get("provider", "")
|
|
94
|
+
v_published = video.get("published", "")
|
|
95
|
+
v_stats = video.get("statistics", {}) or {}
|
|
96
|
+
v_views = v_stats.get("views", 0) if isinstance(v_stats, dict) else 0
|
|
97
|
+
|
|
98
|
+
# Build video info panel
|
|
99
|
+
info_lines = []
|
|
100
|
+
info_lines.append(f"[bold white]{v_title}[/bold white]")
|
|
101
|
+
|
|
102
|
+
meta_parts = []
|
|
103
|
+
if v_duration:
|
|
104
|
+
meta_parts.append(f"[cyan]⏱ {v_duration}[/cyan]")
|
|
105
|
+
if v_views:
|
|
106
|
+
meta_parts.append(f"[green]👁 {_format_views(v_views)} views[/green]")
|
|
107
|
+
if v_published:
|
|
108
|
+
meta_parts.append(f"[dim]{v_published}[/dim]")
|
|
109
|
+
if meta_parts:
|
|
110
|
+
info_lines.append(" • ".join(meta_parts))
|
|
111
|
+
|
|
112
|
+
if v_uploader:
|
|
113
|
+
info_lines.append(f"[yellow]📺 {v_uploader}[/yellow]")
|
|
114
|
+
if v_provider and v_provider.lower() != v_uploader.lower():
|
|
115
|
+
info_lines.append(f"[dim]via {v_provider}[/dim]")
|
|
116
|
+
info_lines.append(f"[blue underline]{v_url}[/blue underline]")
|
|
117
|
+
|
|
118
|
+
panel = Panel(
|
|
119
|
+
"\n".join(info_lines),
|
|
120
|
+
title=f"[bold magenta]#{i}[/bold magenta]",
|
|
121
|
+
title_align="left",
|
|
122
|
+
border_style="dim",
|
|
123
|
+
)
|
|
124
|
+
console.print(panel)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _print_news(data: List[Any], title: str) -> None:
|
|
128
|
+
"""Print news results in a beautiful format."""
|
|
129
|
+
if not data:
|
|
130
|
+
rprint("[bold yellow]No news results found.[/bold yellow]")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
console.print(f"\n[bold cyan]{title}[/bold cyan]\n")
|
|
134
|
+
|
|
135
|
+
for i, article in enumerate(data, 1):
|
|
136
|
+
# Handle both dict and dataclass objects
|
|
137
|
+
if hasattr(article, "title"):
|
|
138
|
+
n_title = article.title
|
|
139
|
+
n_url = article.url
|
|
140
|
+
n_source = getattr(article, "source", "")
|
|
141
|
+
n_date = getattr(article, "date", "")
|
|
142
|
+
n_body = getattr(article, "body", "")
|
|
143
|
+
else:
|
|
144
|
+
n_title = article.get("title", "")
|
|
145
|
+
n_url = article.get("url", "")
|
|
146
|
+
n_source = article.get("source", "")
|
|
147
|
+
n_date = article.get("date", "")
|
|
148
|
+
n_body = article.get("body", "")
|
|
149
|
+
|
|
150
|
+
# Build news info panel
|
|
151
|
+
info_lines = []
|
|
152
|
+
info_lines.append(f"[bold white]{n_title}[/bold white]")
|
|
153
|
+
|
|
154
|
+
meta_parts = []
|
|
155
|
+
if n_source:
|
|
156
|
+
meta_parts.append(f"[yellow]📰 {n_source}[/yellow]")
|
|
157
|
+
if n_date:
|
|
158
|
+
meta_parts.append(f"[dim]🕐 {n_date}[/dim]")
|
|
159
|
+
if meta_parts:
|
|
160
|
+
info_lines.append(" • ".join(meta_parts))
|
|
161
|
+
|
|
162
|
+
if n_body:
|
|
163
|
+
info_lines.append(f"[dim italic]{_truncate(n_body, 150)}[/dim italic]")
|
|
164
|
+
info_lines.append(f"[blue underline]{n_url}[/blue underline]")
|
|
165
|
+
|
|
166
|
+
panel = Panel(
|
|
167
|
+
"\n".join(info_lines),
|
|
168
|
+
title=f"[bold magenta]#{i}[/bold magenta]",
|
|
169
|
+
title_align="left",
|
|
170
|
+
border_style="dim",
|
|
171
|
+
)
|
|
172
|
+
console.print(panel)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _print_suggestions(data: List[Any], title: str) -> None:
|
|
176
|
+
"""Print suggestions in a beautiful format."""
|
|
177
|
+
if not data:
|
|
178
|
+
rprint("[bold yellow]No suggestions found.[/bold yellow]")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
console.print(f"\n[bold cyan]{title}[/bold cyan]\n")
|
|
182
|
+
|
|
183
|
+
table = Table(show_header=True, header_style="bold magenta", box=None, padding=(0, 2))
|
|
184
|
+
table.add_column("#", style="dim", width=4)
|
|
185
|
+
table.add_column("Suggestion", style="bold white")
|
|
186
|
+
table.add_column("Type", style="cyan", width=10)
|
|
187
|
+
table.add_column("Description", style="dim")
|
|
188
|
+
|
|
189
|
+
for i, item in enumerate(data, 1):
|
|
190
|
+
# Handle different formats
|
|
191
|
+
if isinstance(item, str):
|
|
192
|
+
table.add_row(str(i), item, "", "")
|
|
193
|
+
elif hasattr(item, "query"):
|
|
194
|
+
# Dataclass (SuggestionResult)
|
|
195
|
+
query = item.query
|
|
196
|
+
is_entity = getattr(item, "is_entity", False)
|
|
197
|
+
name = getattr(item, "name", "")
|
|
198
|
+
desc = getattr(item, "desc", "")
|
|
199
|
+
type_str = "[green]Entity[/green]" if is_entity else ""
|
|
200
|
+
display = name if name else query
|
|
201
|
+
table.add_row(str(i), display, type_str, _truncate(desc, 50))
|
|
202
|
+
elif isinstance(item, dict):
|
|
203
|
+
query = item.get("query", item.get("suggestion", str(item)))
|
|
204
|
+
is_entity = item.get("is_entity", "False")
|
|
205
|
+
name = item.get("name", "")
|
|
206
|
+
desc = item.get("desc", "")
|
|
207
|
+
type_str = "[green]Entity[/green]" if is_entity in (True, "True") else ""
|
|
208
|
+
display = name if name else query
|
|
209
|
+
table.add_row(str(i), display, type_str, _truncate(desc, 50))
|
|
210
|
+
else:
|
|
211
|
+
table.add_row(str(i), str(item), "", "")
|
|
212
|
+
|
|
213
|
+
console.print(table)
|
|
214
|
+
console.print()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _print_images(data: List[Any], title: str) -> None:
|
|
218
|
+
"""Print image results in a beautiful format."""
|
|
219
|
+
if not data:
|
|
220
|
+
rprint("[bold yellow]No image results found.[/bold yellow]")
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
console.print(f"\n[bold cyan]{title}[/bold cyan]\n")
|
|
224
|
+
|
|
225
|
+
for i, img in enumerate(data, 1):
|
|
226
|
+
# Handle both dict and dataclass objects
|
|
227
|
+
if hasattr(img, "title"):
|
|
228
|
+
i_title = img.title or "Untitled"
|
|
229
|
+
i_url = getattr(img, "url", "")
|
|
230
|
+
i_source = getattr(img, "source", "")
|
|
231
|
+
i_width = getattr(img, "width", "")
|
|
232
|
+
i_height = getattr(img, "height", "")
|
|
233
|
+
else:
|
|
234
|
+
i_title = img.get("title", "Untitled")
|
|
235
|
+
i_url = img.get("url", "")
|
|
236
|
+
i_source = img.get("source", "")
|
|
237
|
+
i_width = img.get("width", "")
|
|
238
|
+
i_height = img.get("height", "")
|
|
239
|
+
|
|
240
|
+
info_lines = []
|
|
241
|
+
info_lines.append(f"[bold white]{_truncate(i_title, 70)}[/bold white]")
|
|
242
|
+
|
|
243
|
+
meta_parts = []
|
|
244
|
+
if i_width and i_height:
|
|
245
|
+
meta_parts.append(f"[cyan]{i_width}x{i_height}[/cyan]")
|
|
246
|
+
if i_source:
|
|
247
|
+
meta_parts.append(f"[yellow]{i_source}[/yellow]")
|
|
248
|
+
if meta_parts:
|
|
249
|
+
info_lines.append(" • ".join(meta_parts))
|
|
250
|
+
|
|
251
|
+
if i_url:
|
|
252
|
+
info_lines.append(f"[blue underline]{_truncate(i_url, 80)}[/blue underline]")
|
|
253
|
+
|
|
254
|
+
panel = Panel(
|
|
255
|
+
"\n".join(info_lines),
|
|
256
|
+
title=f"[bold magenta]#{i}[/bold magenta]",
|
|
257
|
+
title_align="left",
|
|
258
|
+
border_style="dim",
|
|
259
|
+
)
|
|
260
|
+
console.print(panel)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _print_data(data: Any, title: str = "Search Results") -> None:
|
|
264
|
+
"""Prints data in a beautiful table."""
|
|
265
|
+
if not data:
|
|
266
|
+
rprint("[bold yellow]No results found.[/bold yellow]")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
table = Table(title=title, show_header=True, header_style="bold magenta", show_lines=True)
|
|
270
|
+
|
|
271
|
+
if isinstance(data, list) and len(data) > 0:
|
|
272
|
+
first_item = data[0]
|
|
273
|
+
|
|
274
|
+
# Handle dataclass objects by converting to dict
|
|
275
|
+
if hasattr(first_item, "__dataclass_fields__"):
|
|
276
|
+
# Convert dataclass to dict for each item
|
|
277
|
+
data = [
|
|
278
|
+
{k: getattr(item, k, "") for k in first_item.__dataclass_fields__}
|
|
279
|
+
for item in data
|
|
280
|
+
]
|
|
281
|
+
first_item = data[0]
|
|
282
|
+
|
|
283
|
+
if isinstance(first_item, dict):
|
|
284
|
+
# Filter out empty or less useful keys for cleaner display
|
|
285
|
+
all_keys = list(first_item.keys())
|
|
286
|
+
# Prioritize important keys
|
|
287
|
+
priority_keys = ["title", "url", "body", "description", "source", "date"]
|
|
288
|
+
keys = [k for k in priority_keys if k in all_keys]
|
|
289
|
+
keys += [k for k in all_keys if k not in keys]
|
|
290
|
+
|
|
291
|
+
table.add_column("#", style="dim", width=4)
|
|
292
|
+
for key in keys:
|
|
293
|
+
table.add_column(key.capitalize())
|
|
294
|
+
|
|
295
|
+
for i, item in enumerate(data, 1):
|
|
296
|
+
row = [str(i)]
|
|
297
|
+
for key in keys:
|
|
298
|
+
val = item.get(key, "")
|
|
299
|
+
if key in ("body", "description") and val and len(str(val)) > 150:
|
|
300
|
+
val = str(val)[:147] + "..."
|
|
301
|
+
elif key == "url" and val and len(str(val)) > 60:
|
|
302
|
+
val = str(val)[:57] + "..."
|
|
303
|
+
row.append(str(val) if val else "")
|
|
304
|
+
table.add_row(*row)
|
|
305
|
+
else:
|
|
306
|
+
table.add_column("#", style="dim", width=4)
|
|
307
|
+
table.add_column("Result")
|
|
308
|
+
for i, item in enumerate(data, 1):
|
|
309
|
+
table.add_row(str(i), str(item))
|
|
310
|
+
else:
|
|
311
|
+
rprint(f"[bold blue]Result:[/bold blue] {data}")
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
console.print(table)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _print_weather(data: Dict[str, Any]) -> None:
|
|
318
|
+
"""Prints weather data in a clean panel."""
|
|
319
|
+
# Be defensive when reading weather payloads
|
|
320
|
+
current = data.get("current") or {}
|
|
321
|
+
location = data.get("location", "Unknown")
|
|
322
|
+
|
|
323
|
+
temp = current.get("temperature_c", "N/A")
|
|
324
|
+
feels_like = current.get("feels_like_c", "N/A")
|
|
325
|
+
condition = current.get("condition", "N/A")
|
|
326
|
+
humidity = current.get("humidity", "N/A")
|
|
327
|
+
wind_speed = current.get("wind_speed_ms", "N/A")
|
|
328
|
+
wind_dir = current.get("wind_direction", "N/A")
|
|
329
|
+
|
|
330
|
+
weather_info = (
|
|
331
|
+
f"[bold blue]Location:[/bold blue] {location}\n"
|
|
332
|
+
f"[bold blue]Temperature:[/bold blue] {temp}°C (Feels like {feels_like}°C)\n"
|
|
333
|
+
f"[bold blue]Condition:[/bold blue] {condition}\n"
|
|
334
|
+
f"[bold blue]Humidity:[/bold blue] {humidity}%\n"
|
|
335
|
+
f"[bold blue]Wind:[/bold blue] {wind_speed} m/s {wind_dir}°"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
panel = Panel(weather_info, title="Current Weather", border_style="green")
|
|
339
|
+
console.print(panel)
|
|
340
|
+
|
|
341
|
+
if isinstance(data.get("daily_forecast"), list):
|
|
342
|
+
forecast_table = Table(title="5-Day Forecast", show_header=True, header_style="bold cyan")
|
|
343
|
+
forecast_table.add_column("Date")
|
|
344
|
+
forecast_table.add_column("Condition")
|
|
345
|
+
forecast_table.add_column("High")
|
|
346
|
+
forecast_table.add_column("Low")
|
|
347
|
+
|
|
348
|
+
for day in data.get("daily_forecast", [])[:5]:
|
|
349
|
+
date = day.get("date", "N/A")
|
|
350
|
+
condition = day.get("condition", "N/A")
|
|
351
|
+
max_temp = day.get("max_temp_c")
|
|
352
|
+
min_temp = day.get("min_temp_c")
|
|
353
|
+
max_temp_str = (
|
|
354
|
+
f"{max_temp:.1f}°C" if isinstance(max_temp, (int, float)) else str(max_temp)
|
|
355
|
+
)
|
|
356
|
+
min_temp_str = (
|
|
357
|
+
f"{min_temp:.1f}°C" if isinstance(min_temp, (int, float)) else str(min_temp)
|
|
358
|
+
)
|
|
359
|
+
forecast_table.add_row(date, condition, max_temp_str, min_temp_str)
|
|
360
|
+
console.print(forecast_table)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
app: CLI = CLI(name="webscout", help="Search the web with a simple UI", version=__version__)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@app.command()
|
|
367
|
+
def version() -> None:
|
|
368
|
+
"""Show the version of webscout."""
|
|
369
|
+
rprint(f"[bold cyan]webscout version:[/bold cyan] {__version__}")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@app.command()
|
|
373
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
374
|
+
@option("--engine", "-e", help="Search engine (ddg, bing, yahoo, brave, etc.)", default="ddg")
|
|
375
|
+
@option("--region", "-r", help="Region for search results", default=None)
|
|
376
|
+
@option("--safesearch", "-s", help="SafeSearch setting", default="moderate")
|
|
377
|
+
@option("--timelimit", "-t", help="Time limit for results", default=None)
|
|
378
|
+
@option("--max-results", "-m", help="Maximum number of results", type=int, default=10)
|
|
379
|
+
def text(
|
|
380
|
+
keywords: str,
|
|
381
|
+
engine: str,
|
|
382
|
+
region: Optional[str] = None,
|
|
383
|
+
safesearch: str = "moderate",
|
|
384
|
+
timelimit: Optional[str] = None,
|
|
385
|
+
max_results: int = 10,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Perform a text search."""
|
|
388
|
+
try:
|
|
389
|
+
search_engine = _get_engine(engine)
|
|
390
|
+
# Handle region defaults if not provided
|
|
391
|
+
if region is None:
|
|
392
|
+
region = "wt-wt" if engine.lower() in ["ddg", "duckduckgo"] else "us"
|
|
393
|
+
|
|
394
|
+
# Most engines use .text(), some use .search() or .run()
|
|
395
|
+
text_method = getattr(search_engine, "text", None)
|
|
396
|
+
if text_method and callable(text_method):
|
|
397
|
+
results = text_method(
|
|
398
|
+
keywords, region=region, safesearch=safesearch, max_results=max_results
|
|
399
|
+
)
|
|
400
|
+
else:
|
|
401
|
+
run_method = getattr(search_engine, "run", None)
|
|
402
|
+
if run_method and callable(run_method):
|
|
403
|
+
results = run_method(
|
|
404
|
+
keywords, region=region, safesearch=safesearch, max_results=max_results
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
search_method = getattr(search_engine, "search", None)
|
|
408
|
+
if search_method and callable(search_method):
|
|
409
|
+
results = search_method(keywords, max_results=max_results)
|
|
410
|
+
else:
|
|
411
|
+
rprint("[bold red]Error: This engine does not support text search.[/bold red]")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
_print_data(results, title=f"{engine.upper()} Text Search: {keywords}")
|
|
415
|
+
except Exception as e:
|
|
416
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@app.command()
|
|
420
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
421
|
+
@option("--engine", "-e", help="Search engine (ddg, bing, yahoo, brave)", default="ddg")
|
|
422
|
+
@option("--max-results", "-m", help="Maximum number of results", type=int, default=10)
|
|
423
|
+
def images(keywords: str, engine: str, max_results: int) -> None:
|
|
424
|
+
"""Perform an images search."""
|
|
425
|
+
try:
|
|
426
|
+
search_engine = _get_engine(engine)
|
|
427
|
+
method = getattr(search_engine, "images", None)
|
|
428
|
+
if method and callable(method):
|
|
429
|
+
results = method(keywords, max_results=max_results)
|
|
430
|
+
_print_images(results, title=f"{engine.upper()} Image Search: {keywords}")
|
|
431
|
+
else:
|
|
432
|
+
rprint("[bold red]Error: This engine does not support image search.[/bold red]")
|
|
433
|
+
except Exception as e:
|
|
434
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@app.command()
|
|
438
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
439
|
+
@option("--engine", "-e", help="Search engine (ddg, yahoo, brave)", default="ddg")
|
|
440
|
+
@option("--max-results", "-m", help="Maximum number of results", type=int, default=10)
|
|
441
|
+
def videos(keywords: str, engine: str, max_results: int) -> None:
|
|
442
|
+
"""Perform a videos search."""
|
|
443
|
+
try:
|
|
444
|
+
search_engine = _get_engine(engine)
|
|
445
|
+
method = getattr(search_engine, "videos", None)
|
|
446
|
+
if method and callable(method):
|
|
447
|
+
results = method(keywords, max_results=max_results)
|
|
448
|
+
_print_videos(results, title=f"{engine.upper()} Video Search: {keywords}")
|
|
449
|
+
else:
|
|
450
|
+
rprint("[bold red]Error: This engine does not support video search.[/bold red]")
|
|
451
|
+
except Exception as e:
|
|
452
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@app.command()
|
|
456
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
457
|
+
@option("--engine", "-e", help="Search engine (ddg, bing, yahoo, brave)", default="ddg")
|
|
458
|
+
@option("--max-results", "-m", help="Maximum number of results", type=int, default=10)
|
|
459
|
+
def news(keywords: str, engine: str, max_results: int) -> None:
|
|
460
|
+
"""Perform a news search."""
|
|
461
|
+
try:
|
|
462
|
+
search_engine = _get_engine(engine)
|
|
463
|
+
method = getattr(search_engine, "news", None)
|
|
464
|
+
if method and callable(method):
|
|
465
|
+
results = method(keywords, max_results=max_results)
|
|
466
|
+
_print_news(results, title=f"{engine.upper()} News Search: {keywords}")
|
|
467
|
+
else:
|
|
468
|
+
rprint("[bold red]Error: This engine does not support news search.[/bold red]")
|
|
469
|
+
except Exception as e:
|
|
470
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@app.command()
|
|
474
|
+
@option("--location", "-l", help="Location to get weather for", required=True)
|
|
475
|
+
@option("--engine", "-e", help="Search engine (ddg, yahoo)", default="ddg")
|
|
476
|
+
def weather(location: str, engine: str) -> None:
|
|
477
|
+
"""Get weather information."""
|
|
478
|
+
try:
|
|
479
|
+
search_engine = _get_engine(engine)
|
|
480
|
+
method = getattr(search_engine, "weather", None)
|
|
481
|
+
if method and callable(method):
|
|
482
|
+
results = method(location)
|
|
483
|
+
_print_weather(results)
|
|
484
|
+
else:
|
|
485
|
+
rprint("[bold red]Error: This engine does not support weather search.[/bold red]")
|
|
486
|
+
except Exception as e:
|
|
487
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@app.command()
|
|
491
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
492
|
+
@option("--engine", "-e", help="Search engine (ddg, yahoo)", default="ddg")
|
|
493
|
+
def answers(keywords: str, engine: str) -> None:
|
|
494
|
+
"""Perform an answers search."""
|
|
495
|
+
try:
|
|
496
|
+
search_engine = _get_engine(engine)
|
|
497
|
+
method = getattr(search_engine, "answers", None)
|
|
498
|
+
if method and callable(method):
|
|
499
|
+
results = method(keywords)
|
|
500
|
+
_print_data(results, title=f"{engine.upper()} Answers: {keywords}")
|
|
501
|
+
else:
|
|
502
|
+
rprint("[bold red]Error: This engine does not support answers search.[/bold red]")
|
|
503
|
+
except Exception as e:
|
|
504
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@app.command()
|
|
508
|
+
@option("--query", "-q", help="Search query", required=True)
|
|
509
|
+
@option("--engine", "-e", help="Search engine (ddg, bing, yahoo, yep, brave)", default="ddg")
|
|
510
|
+
def suggestions(query: str, engine: str) -> None:
|
|
511
|
+
"""Get search suggestions."""
|
|
512
|
+
try:
|
|
513
|
+
search_engine = _get_engine(engine)
|
|
514
|
+
method = getattr(search_engine, "suggestions", None)
|
|
515
|
+
if method and callable(method):
|
|
516
|
+
results = method(query)
|
|
517
|
+
_print_suggestions(results, title=f"{engine.upper()} Suggestions: {query}")
|
|
518
|
+
else:
|
|
519
|
+
rprint("[bold red]Error: This engine does not support suggestions.[/bold red]")
|
|
520
|
+
except Exception as e:
|
|
521
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@app.command()
|
|
525
|
+
@option("--keywords", "-k", help="Text for translation", required=True)
|
|
526
|
+
@option("--from-lang", "-f", help="Language to translate from", default=None)
|
|
527
|
+
@option("--to", "-t", help="Language to translate to", default="en")
|
|
528
|
+
@option("--engine", "-e", help="Search engine (ddg, yahoo)", default="ddg")
|
|
529
|
+
def translate(
|
|
530
|
+
keywords: str, from_lang: Optional[str] = None, to: str = "en", engine: str = "ddg"
|
|
531
|
+
) -> None:
|
|
532
|
+
"""Perform translation."""
|
|
533
|
+
try:
|
|
534
|
+
search_engine = _get_engine(engine)
|
|
535
|
+
method = getattr(search_engine, "translate", None)
|
|
536
|
+
if method and callable(method):
|
|
537
|
+
results = method(keywords, from_lang=from_lang, to_lang=to)
|
|
538
|
+
_print_data(results, title=f"{engine.upper()} Translation: {keywords}")
|
|
539
|
+
else:
|
|
540
|
+
rprint("[bold red]Error: This engine does not support translation.[/bold red]")
|
|
541
|
+
except Exception as e:
|
|
542
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@app.command()
|
|
546
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
547
|
+
@option("--place", "-p", help="Place name")
|
|
548
|
+
@option("--radius", "-r", help="Search radius (km)", type=int, default=0)
|
|
549
|
+
@option("--engine", "-e", help="Search engine (ddg, yahoo)", default="ddg")
|
|
550
|
+
def maps(keywords: str, place: Optional[str] = None, radius: int = 0, engine: str = "ddg") -> None:
|
|
551
|
+
"""Perform a maps search."""
|
|
552
|
+
try:
|
|
553
|
+
search_engine = _get_engine(engine)
|
|
554
|
+
method = getattr(search_engine, "maps", None)
|
|
555
|
+
if method and callable(method):
|
|
556
|
+
results = method(keywords, place=place, radius=radius)
|
|
557
|
+
_print_data(results, title=f"{engine.upper()} Maps Search: {keywords}")
|
|
558
|
+
else:
|
|
559
|
+
rprint("[bold red]Error: This engine does not support maps search.[/bold red]")
|
|
560
|
+
except Exception as e:
|
|
561
|
+
rprint(f"[bold red]Error:[/bold red] {str(e)}")
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
# Keep search for compatibility/convenience
|
|
565
|
+
@app.command()
|
|
566
|
+
@option("--keywords", "-k", help="Search keywords", required=True)
|
|
567
|
+
@option("--engine", "-e", help="Search engine", default="ddg")
|
|
568
|
+
@option("--max-results", "-m", help="Maximum results", type=int, default=10)
|
|
569
|
+
def search(keywords: str, engine: str, max_results: int) -> None:
|
|
570
|
+
"""Unified search command across all engines."""
|
|
571
|
+
# Call the local `text` function implementation (not a command object)
|
|
572
|
+
text(keywords=keywords, engine=engine, max_results=max_results)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def main():
|
|
576
|
+
"""Main entry point for the CLI."""
|
|
577
|
+
try:
|
|
578
|
+
app.run()
|
|
579
|
+
except Exception as e:
|
|
580
|
+
rprint(f"[bold red]CLI Error:[/bold red] {str(e)}")
|
|
581
|
+
sys.exit(1)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
if __name__ == "__main__":
|
|
585
|
+
main()
|