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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from webscout.search.results import ImagesResult
|
|
7
|
+
|
|
8
|
+
from .base import YepBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class YepImages(YepBase):
|
|
12
|
+
name = "yep"
|
|
13
|
+
category = "images"
|
|
14
|
+
def run(self, *args, **kwargs) -> List[ImagesResult]:
|
|
15
|
+
keywords = args[0] if args else kwargs.get("keywords")
|
|
16
|
+
region = args[1] if len(args) > 1 else kwargs.get("region", "all")
|
|
17
|
+
safesearch = args[2] if len(args) > 2 else kwargs.get("safesearch", "moderate")
|
|
18
|
+
max_results = args[3] if len(args) > 3 else kwargs.get("max_results")
|
|
19
|
+
|
|
20
|
+
safe_search_map = {
|
|
21
|
+
"on": "on",
|
|
22
|
+
"moderate": "moderate",
|
|
23
|
+
"off": "off"
|
|
24
|
+
}
|
|
25
|
+
safe_setting = safe_search_map.get(safesearch.lower(), "moderate")
|
|
26
|
+
|
|
27
|
+
params = {
|
|
28
|
+
"client": "web",
|
|
29
|
+
"gl": region,
|
|
30
|
+
"limit": str(max_results) if max_results else "10",
|
|
31
|
+
"no_correct": "false",
|
|
32
|
+
"q": keywords,
|
|
33
|
+
"safeSearch": safe_setting,
|
|
34
|
+
"type": "images"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
url = f"{self.base_url}?{urlencode(params)}"
|
|
38
|
+
try:
|
|
39
|
+
response = self.session.get(url)
|
|
40
|
+
response.raise_for_status()
|
|
41
|
+
raw_results = response.json()
|
|
42
|
+
|
|
43
|
+
if not raw_results or len(raw_results) < 2:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
formatted_results = []
|
|
47
|
+
results = raw_results[1].get('results', [])
|
|
48
|
+
|
|
49
|
+
for result in results:
|
|
50
|
+
if result.get("type") != "Image":
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
formatted_result = ImagesResult(
|
|
54
|
+
title=self._remove_html_tags(result.get("title", "")),
|
|
55
|
+
image=result.get("image_id", ""),
|
|
56
|
+
thumbnail=result.get("src", ""),
|
|
57
|
+
url=result.get("host_page", ""),
|
|
58
|
+
height=result.get("height", 0),
|
|
59
|
+
width=result.get("width", 0),
|
|
60
|
+
source=result.get("visual_url", "")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
formatted_results.append(formatted_result)
|
|
64
|
+
|
|
65
|
+
if max_results:
|
|
66
|
+
return formatted_results[:max_results]
|
|
67
|
+
return formatted_results
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
resp = getattr(e, 'response', None)
|
|
71
|
+
if resp is not None:
|
|
72
|
+
raise Exception(f"Yep image search failed with status {resp.status_code}: {str(e)}")
|
|
73
|
+
else:
|
|
74
|
+
raise Exception(f"Yep image search failed: {str(e)}")
|
|
75
|
+
|
|
76
|
+
def _remove_html_tags(self, text: str) -> str:
|
|
77
|
+
result = ""
|
|
78
|
+
in_tag = False
|
|
79
|
+
|
|
80
|
+
for char in text:
|
|
81
|
+
if char == '<':
|
|
82
|
+
in_tag = True
|
|
83
|
+
elif char == '>':
|
|
84
|
+
in_tag = False
|
|
85
|
+
elif not in_tag:
|
|
86
|
+
result += char
|
|
87
|
+
|
|
88
|
+
replacements = {
|
|
89
|
+
' ': ' ',
|
|
90
|
+
'&': '&',
|
|
91
|
+
'<': '<',
|
|
92
|
+
'>': '>',
|
|
93
|
+
'"': '"',
|
|
94
|
+
''': "'",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for entity, replacement in replacements.items():
|
|
98
|
+
result = result.replace(entity, replacement)
|
|
99
|
+
|
|
100
|
+
return result.strip()
|
|
101
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from .base import YepBase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class YepSuggestions(YepBase):
|
|
10
|
+
name = "yep"
|
|
11
|
+
category = "suggestions"
|
|
12
|
+
def run(self, *args, **kwargs) -> List[str]:
|
|
13
|
+
keywords = args[0] if args else kwargs.get("keywords")
|
|
14
|
+
region = args[1] if len(args) > 1 else kwargs.get("region", "all")
|
|
15
|
+
|
|
16
|
+
params = {
|
|
17
|
+
"query": keywords,
|
|
18
|
+
"type": "web",
|
|
19
|
+
"gl": region
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
url = f"https://api.yep.com/ac/?{urlencode(params)}"
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
response = self.session.get(url)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
data = response.json()
|
|
28
|
+
if isinstance(data, list) and len(data) > 1 and isinstance(data[1], list):
|
|
29
|
+
return data[1]
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
except Exception as e:
|
|
33
|
+
resp = getattr(e, 'response', None)
|
|
34
|
+
if resp is not None:
|
|
35
|
+
raise Exception(f"Yep suggestions failed with status {resp.status_code}: {str(e)}")
|
|
36
|
+
else:
|
|
37
|
+
raise Exception(f"Yep suggestions failed: {str(e)}")
|
|
38
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from webscout.search.results import TextResult
|
|
7
|
+
|
|
8
|
+
from .base import YepBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class YepSearch(YepBase):
|
|
12
|
+
name = "yep"
|
|
13
|
+
category = "text"
|
|
14
|
+
def run(self, *args, **kwargs) -> List[TextResult]:
|
|
15
|
+
keywords = args[0] if args else kwargs.get("keywords")
|
|
16
|
+
region = args[1] if len(args) > 1 else kwargs.get("region", "all")
|
|
17
|
+
safesearch = args[2] if len(args) > 2 else kwargs.get("safesearch", "moderate")
|
|
18
|
+
max_results = args[3] if len(args) > 3 else kwargs.get("max_results")
|
|
19
|
+
|
|
20
|
+
safe_search_map = {
|
|
21
|
+
"on": "on",
|
|
22
|
+
"moderate": "moderate",
|
|
23
|
+
"off": "off"
|
|
24
|
+
}
|
|
25
|
+
safe_setting = safe_search_map.get(safesearch.lower(), "moderate")
|
|
26
|
+
|
|
27
|
+
params = {
|
|
28
|
+
"client": "web",
|
|
29
|
+
"gl": region,
|
|
30
|
+
"limit": str(max_results) if max_results else "10",
|
|
31
|
+
"no_correct": "false",
|
|
32
|
+
"q": keywords,
|
|
33
|
+
"safeSearch": safe_setting,
|
|
34
|
+
"type": "web"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
url = f"{self.base_url}?{urlencode(params)}"
|
|
38
|
+
try:
|
|
39
|
+
response = self.session.get(url)
|
|
40
|
+
response.raise_for_status()
|
|
41
|
+
raw_results = response.json()
|
|
42
|
+
|
|
43
|
+
formatted_results = self.format_results(raw_results)
|
|
44
|
+
|
|
45
|
+
if max_results:
|
|
46
|
+
return formatted_results[:max_results]
|
|
47
|
+
return formatted_results
|
|
48
|
+
except Exception as e:
|
|
49
|
+
resp = getattr(e, 'response', None)
|
|
50
|
+
if resp is not None:
|
|
51
|
+
raise Exception(f"Yep search failed with status {resp.status_code}: {str(e)}")
|
|
52
|
+
else:
|
|
53
|
+
raise Exception(f"Yep search failed: {str(e)}")
|
|
54
|
+
|
|
55
|
+
def format_results(self, raw_results: dict) -> List[TextResult]:
|
|
56
|
+
formatted_results = []
|
|
57
|
+
|
|
58
|
+
if not raw_results or len(raw_results) < 2:
|
|
59
|
+
return formatted_results
|
|
60
|
+
|
|
61
|
+
results = raw_results[1].get('results', [])
|
|
62
|
+
|
|
63
|
+
for result in results:
|
|
64
|
+
formatted_result = TextResult(
|
|
65
|
+
title=self._remove_html_tags(result.get("title", "")),
|
|
66
|
+
href=result.get("url", ""),
|
|
67
|
+
body=self._remove_html_tags(result.get("snippet", ""))
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
formatted_results.append(formatted_result)
|
|
71
|
+
|
|
72
|
+
return formatted_results
|
|
73
|
+
|
|
74
|
+
def _remove_html_tags(self, text: str) -> str:
|
|
75
|
+
result = ""
|
|
76
|
+
in_tag = False
|
|
77
|
+
|
|
78
|
+
for char in text:
|
|
79
|
+
if char == '<':
|
|
80
|
+
in_tag = True
|
|
81
|
+
elif char == '>':
|
|
82
|
+
in_tag = False
|
|
83
|
+
elif not in_tag:
|
|
84
|
+
result += char
|
|
85
|
+
|
|
86
|
+
replacements = {
|
|
87
|
+
' ': ' ',
|
|
88
|
+
'&': '&',
|
|
89
|
+
'<': '<',
|
|
90
|
+
'>': '>',
|
|
91
|
+
'"': '"',
|
|
92
|
+
''': "'",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for entity, replacement in replacements.items():
|
|
96
|
+
result = result.replace(entity, replacement)
|
|
97
|
+
|
|
98
|
+
return result.strip()
|
|
99
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""HTTP client for search engines."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from random import choice
|
|
6
|
+
from typing import Any, Literal, Optional, cast
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import trio # type: ignore
|
|
10
|
+
except ImportError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
import curl_cffi.requests
|
|
14
|
+
|
|
15
|
+
from ..exceptions import RatelimitE, TimeoutE, WebscoutE
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HttpClient:
|
|
19
|
+
"""HTTP client wrapper for search engines."""
|
|
20
|
+
|
|
21
|
+
# curl_cffi supported browser impersonations
|
|
22
|
+
_impersonates = (
|
|
23
|
+
"chrome99",
|
|
24
|
+
"chrome100",
|
|
25
|
+
"chrome101",
|
|
26
|
+
"chrome104",
|
|
27
|
+
"chrome107",
|
|
28
|
+
"chrome110",
|
|
29
|
+
"chrome116",
|
|
30
|
+
"chrome119",
|
|
31
|
+
"chrome120",
|
|
32
|
+
"chrome123",
|
|
33
|
+
"chrome124",
|
|
34
|
+
"chrome131",
|
|
35
|
+
"chrome133a",
|
|
36
|
+
"chrome99_android",
|
|
37
|
+
"chrome131_android",
|
|
38
|
+
"safari15_3",
|
|
39
|
+
"safari15_5",
|
|
40
|
+
"safari17_0",
|
|
41
|
+
"safari17_2_ios",
|
|
42
|
+
"safari18_0",
|
|
43
|
+
"safari18_0_ios",
|
|
44
|
+
"edge99",
|
|
45
|
+
"edge101",
|
|
46
|
+
"firefox133",
|
|
47
|
+
"firefox135",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
proxy: str | None = None,
|
|
53
|
+
timeout: int | None = 10,
|
|
54
|
+
verify: bool = True,
|
|
55
|
+
headers: dict[str, str] | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Initialize HTTP client.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
proxy: Proxy URL (supports http/https/socks5).
|
|
61
|
+
timeout: Request timeout in seconds.
|
|
62
|
+
verify: Whether to verify SSL certificates.
|
|
63
|
+
headers: Default headers for requests.
|
|
64
|
+
"""
|
|
65
|
+
self.proxy = proxy
|
|
66
|
+
self.timeout = timeout
|
|
67
|
+
self.verify = verify
|
|
68
|
+
|
|
69
|
+
# Choose random browser to impersonate
|
|
70
|
+
impersonate_browser = choice(self._impersonates)
|
|
71
|
+
|
|
72
|
+
# Initialize curl_cffi session
|
|
73
|
+
self.client = curl_cffi.requests.Session(
|
|
74
|
+
headers=headers or {},
|
|
75
|
+
proxies={"http": self.proxy, "https": self.proxy} if self.proxy else None,
|
|
76
|
+
timeout=cast(Any, timeout),
|
|
77
|
+
impersonate=impersonate_browser,
|
|
78
|
+
verify=verify,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def request(
|
|
82
|
+
self,
|
|
83
|
+
method: Literal["GET", "POST", "HEAD", "OPTIONS", "DELETE", "PUT", "PATCH"],
|
|
84
|
+
url: str,
|
|
85
|
+
params: dict[str, Any] | None = None,
|
|
86
|
+
data: dict[str, Any] | bytes | None = None,
|
|
87
|
+
json: Any = None,
|
|
88
|
+
headers: dict[str, str] | None = None,
|
|
89
|
+
cookies: dict[str, str] | None = None,
|
|
90
|
+
timeout: int | None = None,
|
|
91
|
+
**kwargs: Any,
|
|
92
|
+
) -> curl_cffi.requests.Response:
|
|
93
|
+
"""Make HTTP request.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
method: HTTP method.
|
|
97
|
+
url: Request URL.
|
|
98
|
+
params: URL parameters.
|
|
99
|
+
data: Request body data.
|
|
100
|
+
json: JSON data to send.
|
|
101
|
+
headers: Request headers.
|
|
102
|
+
cookies: Request cookies.
|
|
103
|
+
timeout: Request timeout (overrides default).
|
|
104
|
+
**kwargs: Additional arguments passed to curl_cffi.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Response object.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
TimeoutE: Request timeout.
|
|
111
|
+
RatelimitE: Rate limit exceeded.
|
|
112
|
+
WebscoutE: Other request errors.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
request_kwargs: dict[str, Any] = {
|
|
116
|
+
"params": params,
|
|
117
|
+
"headers": headers,
|
|
118
|
+
"json": json,
|
|
119
|
+
"timeout": timeout or self.timeout,
|
|
120
|
+
**kwargs,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if isinstance(cookies, dict):
|
|
124
|
+
request_kwargs["cookies"] = cookies
|
|
125
|
+
|
|
126
|
+
if data is not None:
|
|
127
|
+
request_kwargs["data"] = data
|
|
128
|
+
|
|
129
|
+
resp = self.client.request(method, url, **request_kwargs)
|
|
130
|
+
|
|
131
|
+
# Check response status
|
|
132
|
+
if resp.status_code == 200:
|
|
133
|
+
return resp
|
|
134
|
+
elif resp.status_code in (202, 301, 403, 400, 429, 418):
|
|
135
|
+
raise RatelimitE(f"{resp.url} {resp.status_code} Rate limit")
|
|
136
|
+
else:
|
|
137
|
+
raise WebscoutE(f"{resp.url} returned {resp.status_code}")
|
|
138
|
+
|
|
139
|
+
except Exception as ex:
|
|
140
|
+
if "time" in str(ex).lower() or "timeout" in str(ex).lower():
|
|
141
|
+
raise TimeoutE(f"{url} {type(ex).__name__}: {ex}") from ex
|
|
142
|
+
raise WebscoutE(f"{url} {type(ex).__name__}: {ex}") from ex
|
|
143
|
+
|
|
144
|
+
def get(self, url: str, **kwargs: Any) -> curl_cffi.requests.Response:
|
|
145
|
+
"""Make GET request."""
|
|
146
|
+
return self.request("GET", url, **kwargs)
|
|
147
|
+
|
|
148
|
+
def post(self, url: str, **kwargs: Any) -> curl_cffi.requests.Response:
|
|
149
|
+
"""Make POST request."""
|
|
150
|
+
return self.request("POST", url, **kwargs)
|
|
151
|
+
|
|
152
|
+
def set_cookies(self, url: str, cookies: dict[str, str]) -> None:
|
|
153
|
+
"""Set cookies for a domain.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
url: URL to set cookies for.
|
|
157
|
+
cookies: Cookie dictionary.
|
|
158
|
+
"""
|
|
159
|
+
self.client.cookies.update(cookies)
|
|
160
|
+
|
|
161
|
+
def close(self) -> None:
|
|
162
|
+
"""Close the HTTP client."""
|
|
163
|
+
if hasattr(self.client, "close"):
|
|
164
|
+
self.client.close()
|
|
165
|
+
|
|
166
|
+
def __enter__(self) -> HttpClient:
|
|
167
|
+
"""Context manager entry."""
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def __exit__(self, *args: Any) -> None:
|
|
171
|
+
"""Context manager exit."""
|
|
172
|
+
self.close()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Result models for search engines."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TextResult:
|
|
11
|
+
"""Text search result."""
|
|
12
|
+
|
|
13
|
+
title: str = ""
|
|
14
|
+
href: str = ""
|
|
15
|
+
body: str = ""
|
|
16
|
+
|
|
17
|
+
def to_dict(self) -> dict[str, Any]:
|
|
18
|
+
"""Convert to dictionary."""
|
|
19
|
+
return {
|
|
20
|
+
"title": self.title,
|
|
21
|
+
"href": self.href,
|
|
22
|
+
"body": self.body,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ImagesResult:
|
|
28
|
+
"""Images search result."""
|
|
29
|
+
|
|
30
|
+
title: str = ""
|
|
31
|
+
image: str = ""
|
|
32
|
+
thumbnail: str = ""
|
|
33
|
+
url: str = ""
|
|
34
|
+
height: int = 0
|
|
35
|
+
width: int = 0
|
|
36
|
+
source: str = ""
|
|
37
|
+
|
|
38
|
+
def to_dict(self) -> dict[str, Any]:
|
|
39
|
+
"""Convert to dictionary."""
|
|
40
|
+
return {
|
|
41
|
+
"title": self.title,
|
|
42
|
+
"image": self.image,
|
|
43
|
+
"thumbnail": self.thumbnail,
|
|
44
|
+
"url": self.url,
|
|
45
|
+
"height": self.height,
|
|
46
|
+
"width": self.width,
|
|
47
|
+
"source": self.source,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class VideosResult:
|
|
53
|
+
"""Videos search result."""
|
|
54
|
+
|
|
55
|
+
content: str = ""
|
|
56
|
+
description: str = ""
|
|
57
|
+
duration: str = ""
|
|
58
|
+
embed_html: str = ""
|
|
59
|
+
embed_url: str = ""
|
|
60
|
+
image_token: str = ""
|
|
61
|
+
images: dict[str, str] = field(default_factory=dict)
|
|
62
|
+
provider: str = ""
|
|
63
|
+
published: str = ""
|
|
64
|
+
publisher: str = ""
|
|
65
|
+
statistics: dict[str, int] = field(default_factory=dict)
|
|
66
|
+
title: str = ""
|
|
67
|
+
uploader: str = ""
|
|
68
|
+
url: str = ""
|
|
69
|
+
thumbnail: str = ""
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
"""Convert to dictionary."""
|
|
73
|
+
return {
|
|
74
|
+
"content": self.content,
|
|
75
|
+
"description": self.description,
|
|
76
|
+
"duration": self.duration,
|
|
77
|
+
"embed_html": self.embed_html,
|
|
78
|
+
"embed_url": self.embed_url,
|
|
79
|
+
"image_token": self.image_token,
|
|
80
|
+
"images": self.images,
|
|
81
|
+
"provider": self.provider,
|
|
82
|
+
"published": self.published,
|
|
83
|
+
"publisher": self.publisher,
|
|
84
|
+
"statistics": self.statistics,
|
|
85
|
+
"title": self.title,
|
|
86
|
+
"uploader": self.uploader,
|
|
87
|
+
"url": self.url,
|
|
88
|
+
"thumbnail": self.thumbnail,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class NewsResult:
|
|
94
|
+
"""News search result."""
|
|
95
|
+
|
|
96
|
+
date: str = ""
|
|
97
|
+
title: str = ""
|
|
98
|
+
body: str = ""
|
|
99
|
+
url: str = ""
|
|
100
|
+
image: str = ""
|
|
101
|
+
source: str = ""
|
|
102
|
+
|
|
103
|
+
def to_dict(self) -> dict[str, Any]:
|
|
104
|
+
"""Convert to dictionary."""
|
|
105
|
+
return {
|
|
106
|
+
"date": self.date,
|
|
107
|
+
"title": self.title,
|
|
108
|
+
"body": self.body,
|
|
109
|
+
"url": self.url,
|
|
110
|
+
"image": self.image,
|
|
111
|
+
"source": self.source,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class BooksResult:
|
|
117
|
+
"""Books search result."""
|
|
118
|
+
|
|
119
|
+
title: str = ""
|
|
120
|
+
author: str = ""
|
|
121
|
+
href: str = ""
|
|
122
|
+
thumbnail: str = ""
|
|
123
|
+
year: str = ""
|
|
124
|
+
publisher: str = ""
|
|
125
|
+
language: str = ""
|
|
126
|
+
filesize: str = ""
|
|
127
|
+
extension: str = ""
|
|
128
|
+
|
|
129
|
+
def to_dict(self) -> dict[str, Any]:
|
|
130
|
+
"""Convert to dictionary."""
|
|
131
|
+
return {
|
|
132
|
+
"title": self.title,
|
|
133
|
+
"author": self.author,
|
|
134
|
+
"href": self.href,
|
|
135
|
+
"thumbnail": self.thumbnail,
|
|
136
|
+
"year": self.year,
|
|
137
|
+
"publisher": self.publisher,
|
|
138
|
+
"language": self.language,
|
|
139
|
+
"filesize": self.filesize,
|
|
140
|
+
"extension": self.extension,
|
|
141
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Yahoo unified search interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from .base import BaseSearch
|
|
8
|
+
from .engines.yahoo.answers import YahooAnswers
|
|
9
|
+
from .engines.yahoo.images import YahooImages
|
|
10
|
+
from .engines.yahoo.maps import YahooMaps
|
|
11
|
+
from .engines.yahoo.news import YahooNews
|
|
12
|
+
from .engines.yahoo.suggestions import YahooSuggestions
|
|
13
|
+
from .engines.yahoo.text import YahooText
|
|
14
|
+
from .engines.yahoo.translate import YahooTranslate
|
|
15
|
+
from .engines.yahoo.videos import YahooVideos
|
|
16
|
+
from .engines.yahoo.weather import YahooWeather
|
|
17
|
+
from .results import ImagesResult, NewsResult, TextResult, VideosResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class YahooSearch(BaseSearch):
|
|
21
|
+
"""Unified Yahoo search interface."""
|
|
22
|
+
|
|
23
|
+
def text(self, keywords: str, region: str = "us", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
24
|
+
search = YahooText()
|
|
25
|
+
return search.run(keywords=keywords, region=region, safesearch=safesearch, max_results=max_results)
|
|
26
|
+
|
|
27
|
+
def images(self, keywords: str, region: str = "us", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
28
|
+
search = YahooImages()
|
|
29
|
+
return search.run(keywords=keywords, region=region, safesearch=safesearch, max_results=max_results)
|
|
30
|
+
|
|
31
|
+
def videos(self, keywords: str, region: str = "us", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
32
|
+
search = YahooVideos()
|
|
33
|
+
return search.run(keywords=keywords, region=region, safesearch=safesearch, max_results=max_results)
|
|
34
|
+
|
|
35
|
+
def news(self, keywords: str, region: str = "us", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
36
|
+
search = YahooNews()
|
|
37
|
+
return search.run(keywords=keywords, region=region, safesearch=safesearch, max_results=max_results)
|
|
38
|
+
|
|
39
|
+
def suggestions(self, keywords: str, region: str = "us") -> List[str]:
|
|
40
|
+
search = YahooSuggestions()
|
|
41
|
+
return search.run(keywords, region)
|
|
42
|
+
|
|
43
|
+
def answers(self, keywords: str) -> List[Dict[str, str]]:
|
|
44
|
+
search = YahooAnswers()
|
|
45
|
+
return search.run(keywords)
|
|
46
|
+
|
|
47
|
+
def maps(self, keywords: str, place: Optional[str] = None, street: Optional[str] = None, city: Optional[str] = None, county: Optional[str] = None, state: Optional[str] = None, country: Optional[str] = None, postalcode: Optional[str] = None, latitude: Optional[str] = None, longitude: Optional[str] = None, radius: int = 0, max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
48
|
+
search = YahooMaps()
|
|
49
|
+
return search.run(keywords, place, street, city, county, state, country, postalcode, latitude, longitude, radius, max_results)
|
|
50
|
+
|
|
51
|
+
def translate(self, keywords: str, from_lang: Optional[str] = None, to_lang: str = "en") -> List[Dict[str, str]]:
|
|
52
|
+
search = YahooTranslate()
|
|
53
|
+
return search.run(keywords, from_lang, to_lang)
|
|
54
|
+
|
|
55
|
+
def weather(self, keywords: str) -> List[Dict[str, str]]:
|
|
56
|
+
search = YahooWeather()
|
|
57
|
+
return search.run(keywords)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Yep unified search interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from .base import BaseSearch
|
|
8
|
+
from .engines.yep.images import YepImages
|
|
9
|
+
from .engines.yep.suggestions import YepSuggestions
|
|
10
|
+
from .engines.yep.text import YepSearch as YepTextSearch
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class YepSearch(BaseSearch):
|
|
14
|
+
"""Unified Yep search interface."""
|
|
15
|
+
|
|
16
|
+
def text(self, keywords: str, region: str = "all", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
17
|
+
search = YepTextSearch()
|
|
18
|
+
results = search.run(keywords, region, safesearch, max_results)
|
|
19
|
+
return [r.to_dict() for r in results]
|
|
20
|
+
|
|
21
|
+
def images(self, keywords: str, region: str = "all", safesearch: str = "moderate", max_results: Optional[int] = None) -> List[Dict[str, str]]:
|
|
22
|
+
search = YepImages()
|
|
23
|
+
results = search.run(keywords, region, safesearch, max_results)
|
|
24
|
+
return [r.to_dict() for r in results]
|
|
25
|
+
|
|
26
|
+
def suggestions(self, keywords: str, region: str = "all") -> List[str]:
|
|
27
|
+
search = YepSuggestions()
|
|
28
|
+
return search.run(keywords, region)
|
|
29
|
+
|
|
30
|
+
def videos(self, *args, **kwargs) -> List[Dict[str, str]]:
|
|
31
|
+
"""Videos search not supported by Yep."""
|
|
32
|
+
raise NotImplementedError("Yep does not support video search")
|
|
33
|
+
|
|
34
|
+
def news(self, *args, **kwargs) -> List[Dict[str, str]]:
|
|
35
|
+
"""News search not supported by Yep."""
|
|
36
|
+
raise NotImplementedError("Yep does not support news search")
|
|
37
|
+
|
|
38
|
+
def answers(self, *args, **kwargs) -> List[Dict[str, str]]:
|
|
39
|
+
"""Instant answers not supported by Yep."""
|
|
40
|
+
raise NotImplementedError("Yep does not support instant answers")
|
|
41
|
+
|
|
42
|
+
def maps(self, *args, **kwargs) -> List[Dict[str, str]]:
|
|
43
|
+
"""Maps search not supported by Yep."""
|
|
44
|
+
raise NotImplementedError("Yep does not support maps search")
|
|
45
|
+
|
|
46
|
+
def translate(self, *args, **kwargs) -> List[Dict[str, str]]:
|
|
47
|
+
"""Translation not supported by Yep."""
|
|
48
|
+
raise NotImplementedError("Yep does not support translation")
|